1
\$\begingroup\$

I made the login flow without any reference to a tutorial. So I want some feedback.

Basically it sends username and password in a POST request to /token. The app stores the tokens on device, then uses the auth token to request customer profile data.

LoginPageViewModel

public class LoginPageViewModel
{
    public LoginPageViewModel(INavigationService navigationService, IEventAggregator eventAggregator, IRepository repository, IProperties properties, ITokenService tokenService, ICustomerService customerService, ISubscriptionService subscriptionService, ITicketService ticketService, ITranslationService translation, IUserDialogs dialogs, IConfiguration configuration, IConnectivity connectivity)
    {
        _navigation = navigationService;
        _eventAggregator = eventAggregator;
        _repository = repository;
        _properties = properties;
        _tokenService = tokenService;
        _customerService = customerService;
        _translation = translation;
        _dialogs = dialogs;

        eventAggregator.GetEvent<LoggedInEvent>().Subscribe(async () => await LoggedInEventHandler());
    }

    private readonly INavigationService _navigation;
    private readonly IEventAggregator _eventAggregator;
    private readonly IRepository _repository;
    private readonly IProperties _properties;
    private readonly ITokenService _tokenService;
    private readonly ICustomerService _customerService;
    private readonly ITranslationService _translation;
    private readonly IUserDialogs _dialogs;

    public string Username { get; set; }
    public string Password { get; set; }
    public ICommand LoginCommand => new Command(async () => await Login());

    private async Task LoggedInEventHandler()
    {
        if (_navigation.GetCurrentPageName() == "LoginPage")
        {
            await _navigation.NavigateAsync("/MainMasterDetailPage/NavigationPage/ContentPage");
        }
    }

    private async Task Login()
    {
        if (string.IsNullOrWhiteSpace(Username) || string.IsNullOrWhiteSpace(Password))
        {
            return;
        }

        _eventAggregator.GetEvent<LoggingInEvent>().Publish();

        TokenData getTokenResponse;

        try
        {
            getTokenResponse = await _tokenService.GetTokens(Username, Password);
        }
        catch (BadRequestException)
        {
            _dialogs.Alert(_translation.GetString("ProfilePageInvalidUsernameOrPassword"));
            _eventAggregator.GetEvent<LoginFailedEvent>().Publish();
            return;
        }
        catch (Exception ex)
        {
            _dialogs.Alert(_translation.GetString("ProfilePageOtherFault"));

            Crashes.TrackError(ex, new Dictionary<string, string>
            {
                { "Event", "LoginAndGetTokens" },
                { "Subject", Username },
                { "Message", ex.Message },
            });

            _eventAggregator.GetEvent<LoginFailedEvent>().Publish();
            return;
        }

        _properties.AccessToken = getTokenResponse.AccessToken;
        _properties.AccessTokenExpires = DateTime.UtcNow.AddSeconds(getTokenResponse.ExpiresIn);
        _properties.RefreshToken = getTokenResponse.RefreshToken;

        try
        {
            var getCustomerResponse = await _customerService.GetCustomer(_properties.AccessToken);
            _repository.Customer = getCustomerResponse.Customer;

            _eventAggregator.GetEvent<CustomerRetrievedEvent>().Publish(getCustomerResponse.Customer);
            _eventAggregator.GetEvent<LoggedInEvent>().Publish();
        }
        catch (Exception ex)
        {
            _dialogs.Alert(_translation.GetString("ProfilePageOtherFault"));

            Crashes.TrackError(ex, new Dictionary<string, string>
            {
                { "Event", "GetCustomerAfterLogin" },
                { "Subject", Username },
                { "Message", ex.Message }
            });

            _eventAggregator.GetEvent<LoginFailedEvent>().Publish();
        }
    }
}

TokenService

public class TokenService : ITokenService
{
    public TokenService(IRequestService requestService)
    {
        _requestService = requestService;
    }

    private readonly IRequestService _requestService;

    public Task<TokenData> GetTokens(string loginName, string password)
    {
        var getTokenRequest = new RestRequest("token", Method.POST);

        getTokenRequest.AddParameter("client_id", "XamarinApp", ParameterType.GetOrPost);
        getTokenRequest.AddParameter("grant_type", "password", ParameterType.GetOrPost);
        getTokenRequest.AddParameter("username", loginName, ParameterType.GetOrPost);
        getTokenRequest.AddParameter("password", password, ParameterType.GetOrPost);

        return _requestService.DoRequest<TokenData>(getTokenRequest);
    }
}
public class TokenData
{
    [JsonProperty("access_token")]
    public string AccessToken { get; set; }
    [JsonProperty("token_type")]
    public string TokenType { get; set; }
    [JsonProperty("expires_in")]
    public long ExpiresIn { get; set; }
    [JsonProperty("refresh_token")]
    public string RefreshToken { get; set; }
    [JsonProperty("as:client_id")]
    public string ClientId { get; set; }
    [JsonProperty("userName")]
    public string UserName { get; set; }
}

RequestService

public class RequestService : IRequestService
{
    private readonly IConnectivity _connectivity;
    private readonly IRestClient _restClient;

    public RequestService(IConnectivity connectivity, IRestClient restClient)
    {
        _connectivity = connectivity;
        _restClient = restClient;
    }

    public async Task<TResult> DoRequest<TResult>(IRestRequest request)
    {
        if (!_connectivity.IsConnected)
        {
            throw new NoConnectionException();
        }

        var response = await _restClient.ExecuteTaskAsync<TResult>(request);

        if (response.StatusCode != HttpStatusCode.OK)
        {
            if (response.StatusCode == HttpStatusCode.Unauthorized)
            {
                throw new UnauthorizedException(response.Content);
            }
            else if (response.StatusCode == HttpStatusCode.BadRequest)
            {
                throw new BadRequestException(response.Content);
            }
            else if (response.StatusCode == HttpStatusCode.NotFound)
            {
                throw new NotFoundException(response.Content);
            }
            else
            {
                throw new HttpRequestException(response.Content);
            }
        }

        return response.Data;
    }
}

Example response when POST to /token

{
    "access_token": "-UHguElQMme12_aIQ305C5pFBwV1X-qAyT1rO2quJcqXIjOCwd73kAwAVCiyfIsThoWa8LPgVmTOyBWG0rBa_5GsaAt8w-O2njL8SNBEJQma47IlGsL53jGJzAVfy2xk37GJLdmkvYOQRZF3u_ejOEx0XhYUNxg-Ph2IjV5EMgTWXrUjoUbiw8V7feonH1QpDFjgN7sZrDcKsLqzG0900yUVaqliCwPmSe6pcfa7ybEHyBG8KC7rihWqNcwMOx9yfwbDVAVY0ZzOJaNT0k0G1sRu0t4KHKr28I7EW_R-zCQVHVCq5uimYcL1VDJRzbNRz83GUddXT6OmQfW5PjTmUYqAPMl3JcyBkv5ko4R0kHB9v0Yp_Sb-4oJasOraF6c3wdqpXJAUTxIHGZy6WIeXJRwr1ZuFmngx_eiUafPsKxEcTTyvPmgPkV36FHam9FBl",
    "token_type": "bearer",
    "expires_in": 86399,
    "refresh_token": "210a2106fd564aee9a75daeb73458fa2",
    "as:client_id": "XamarinApp",
    "userName": "[email protected]",
    ".issued": "Wed, 01 May 2019 10:10:06 GMT",
    ".expires": "Thu, 02 May 2019 10:10:06 GMT"
}

The Web API was setup according to Token Based Authentication using ASP.NET Web API 2, Owin, and Identity.

\$\endgroup\$

1 Answer 1

0
\$\begingroup\$

Simplify your RequestService by inverting the if-statement in DoRequest():

public async Task<TResult> DoRequest<TResult>(IRestRequest request)
{
    if (!_connectivity.IsConnected)
    {
        throw new NoConnectionException();
    }

    var response = await _restClient.ExecuteTaskAsync<TResult>(request);

    // If Ok, return ...
    if (response.StatusCode == HttpStatusCode.OK)
    {
        return response.Data;
    }

    // ... so this block is not nested
    if (response.StatusCode == HttpStatusCode.Unauthorized)
    {
        throw new UnauthorizedException(response.Content);
    }
    else if (response.StatusCode == HttpStatusCode.BadRequest)
    {
        throw new BadRequestException(response.Content);
    }
    else if (response.StatusCode == HttpStatusCode.NotFound)
    {
        throw new NotFoundException(response.Content);
    }
    else
    {
        throw new HttpRequestException(response.Content);
    }
}

Since you always compare the status code, make sure to use a switch statement. This results in much shorter and more readable code.

public async Task<TResult> DoRequest<TResult>(IRestRequest request)
{
    if (!_connectivity.IsConnected)
    {
        throw new NoConnectionException();
    }

    var response = await _restClient.ExecuteTaskAsync<TResult>(request);

    switch (response.StatusCode)
    {
        case HttpStatusCode.OK:
            return response.Data;
        case HttpStatusCode.Unauthorized:
            throw new UnauthorizedException(response.Content);
        case HttpStatusCode.BadRequest:
            throw new BadRequestException(response.Content);
        case HttpStatusCode.NotFound:
            throw new NotFoundException(response.Content);
        default:
            throw new HttpRequestException(response.Content);
    }
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.