Простая реализация Token для взаимодействия мобильного приложения с WebAPI

в 12:50, , рубрики: api, C#, httpclient, Visual Studio, webapi, xamarin, xamarin.forms, разработка мобильных приложений

С недавнего времени занялся разработкой мобильных приложений с помощью Xamarin.Forms в связи с производственной так сказать необходимостью. Не буду конечно рассказывать про танцы с бубнами чтобы написать и запустить на эмуляторе приложение «Hello, World!», но главное разработка пошла достаточно плавно.

Благо и понимание задачи было — а именно — взаимодействие мобильного приложения с базой данных внутренней CRM системы в компании, добавить сотрудникам мобильности, но при этом не забывать и о безопасности. Было принято решение создать WebAPI, ибо чтобы работать с уже привычными ASMX веб-сервисами в Xamarin нужно плясать с бубнами.

Как сказал выше, в том числе хотелось сделать «связующее звено» достаточно безопасным, а значит мобильное приложение должно иметь авторизацию (до кучи и удобства с возможностью сохранения авторизации и автоматического входа.

Не хотелось глубоко копаться в реализации WebAPI с авторизацией на уровне Token, а хотелось сделать что-то попроще, тем более пока «гуглил» видел что такого желания у людей с избытком, но все отсылы отвечающих были либо к стандартным механизмам, либо использования каких-нибудь пакетов из NuGet, чего хотелось бы по максимуму избежать.

В базе собственной CRM и так уже есть вся информация для авторизации и городить что-то лишнее тупо не хотелось.

В итоге, после долгих мытарств, поисков и т.п. — думаю у меня получилось достаточно неплохое решение, которым мне и хочется поделится с сообществом.

Итак, что мы имеем:

Авторизация и получение Token

Не буду опять-таки вдаваться в подробности создания WebAPI, приведу пример кода функции авторизации.

[HttpPost]
public LoginResult Logining([FromBody] LoginInfo LogInf)
   {
   if (dc.Managers.Count(w => w.EMail.StartsWith(LogInf.PortalUserEMail)) > 0)
      {
         if (dc.Managers.Count(w => w.EMail.StartsWith(LogInf.PortalUserEMail) && w.Password == LogInf.PortalUserPassword) > 0)
            {
               Managers _man = dc.Managers.First(w => w.EMail.StartsWith(LogInf.PortalUserEMail));
               _man.Token = GenerateToken();
               dc.SubmitChanges();
               return new LoginResult()
                  {
                     Error = null, Token = _man.Token, ValidUser = true,
                     managerInfo = new ManagerInfo() { ManagerId = _man.Id, ManagerName = _man.ManagerName }
                  };
            }
            else
            {
               return new LoginResult()
                  {
                     Error = "Неверный пароль!", Token = null, ValidUser = false, managerInfo = null
                  };
            }
      }
      else
      {
         return new LoginResult()
            {
               Error = "Такого пользователя не существует!", Token = null, ValidUser = false, managerInfo = null
            };
      }
   }

Объяснять что делает функция думаю тоже нет смысла, но как результат при вызове API мобильное приложение получает информацию что юзер валиден и выдаёт сгенерированный Token (я решил не мелочится и заложил генерацию 1000-символьной строки из большого количества символов всей английской и русской клавиатуры, с заглавными и строчными буквами, цифр и простых символов.

Этот «псевдо»-Token я прописываю в

App.Current.Properties["Token"] = rez.Token;

приложения.

Кстати стоит, как мне кажется, отдельно отметить 3 потраченных дня, откаты версий и т.п. чтобы разобраться с этим самым App.Current.Properties.

Произошла ситуация, что в какой-то момент при перезапуске приложения на эмуляторе содержимое App.Current.Properties отсутствовало. Долго мучился и пытался понять почему всё пропадает.

Оказывается пока приложение активно в App.Current.Properties могут хранится любые объекты, в том числе и объекты с данными собственных классов, но при «убиении» процесса если там было что-то отличное от «простых» объектов — содержимое App.Current.Properties отчищается, но если там хранить только простые объекты — string, bool, int и т.п. то всё останется сохраненным!

Продолжим. Все последующие обращения к API я снабжаю дополнительным заголовком:

var client = new HttpClient();
var address = $"http://хх.ххх.х.хх/SomeWebApi/api/Works?ManId=" + ManagerId;
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Add("Token", (string)App.Current.Properties["Token"]);
HttpResponseMessage response = await client.GetAsync(address);

Что по сути к заголовкам запроса добавляет хидер. Теперь все остальные контроллеры WebAPI перед тем как что-либо «выдать» клиенту проверяют наличие и соответствие псевдо-Token.

К примеру:

public IEnumerable<WorkInformation> Get([FromUri] int ManId)
   {
      IEnumerable<string> UserToken;
      if (!Request.Headers.TryGetValues("Token", out UserToken))
         {
            return null;
         }
      Managers _CurrManager = dc.Managers.First(w => w.Id == ManId);
      List<WorkInformation> _list = new List<WorkInformation>();
      if (_CurrManager.Token != UserToken.ToArray()[0])
         {
            _list.Add(new WorkInformation() { id = "-1", Client = "Ошибка проверки ключа защиты", DTime = DateTime.Now, Comment = "Перезайдите в приложение", EventImageName = "" });
            return _list;
         }
      //если всё ОК делаем что надо
   }

Как мне кажется полученная реализация псевдо-токена имеет право на существование и кому-то может помочь в реализациях. Надеюсь, сообщество палками меня не закидает, это мой первый пост, надеюсь что будет не только критика.

Автор: IT-AleX

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js