Reputation: 29
I'm using the azure mobile services sdk to do offline sync. I made my api so that it is protected with basic authentication using email and password.
How can I embed these credentials with the MobileServiceClient, so that whenever I call a method it has the correct auth credentials.
this is my existing code for the MobileServiceClient.
var handler = new AuthHandler();
//TODO 1: Create our client
//Create our client
MobileService = new MobileServiceClient(Helpers.Keys.AzureServiceUrl, handler)
{
SerializerSettings = new MobileServiceJsonSerializerSettings()
{
CamelCasePropertyNames = true
}
};
//assign mobile client to handler
handler.Client = MobileService;
MobileService.CurrentUser = new MobileServiceUser(Settings.UserId);
MobileService.CurrentUser.MobileServiceAuthenticationToken = Settings.AuthToken;
AuthHandler Class
class AuthHandler : DelegatingHandler
{
public IMobileServiceClient Client { get; set; }
private static readonly SemaphoreSlim semaphore = new SemaphoreSlim(1);
private static bool isReauthenticating = false;
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
//Clone the request in case we need to send it again
var clonedRequest = await CloneRequest(request);
var response = await base.SendAsync(clonedRequest, cancellationToken);
//If the token is expired or is invalid, then we need to either refresh the token or prompt the user to log back in
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
if (isReauthenticating)
return response;
var service = DependencyService.Get<AzureService>();
var client = new MobileServiceClient(Helpers.Keys.AzureServiceUrl, null);
client.CurrentUser = new MobileServiceUser(Settings.UserId);
client.CurrentUser.MobileServiceAuthenticationToken = Settings.AuthToken;
string authToken = client.CurrentUser.MobileServiceAuthenticationToken;
await semaphore.WaitAsync();
//In case two threads enter this method at the same time, only one should do the refresh (or re-login), the other should just resend the request with an updated header.
if (authToken != client.CurrentUser.MobileServiceAuthenticationToken) // token was already renewed
{
semaphore.Release();
return await ResendRequest(client, request, cancellationToken);
}
isReauthenticating = true;
bool gotNewToken = false;
try
{
gotNewToken = await RefreshToken(client);
//Otherwise if refreshing the token failed or Facebook\Twitter is being used, prompt the user to log back in via the login screen
if (!gotNewToken)
{
gotNewToken = await service.LoginAsync();
}
}
catch (System.Exception e)
{
Debug.WriteLine("Unable to refresh token: " + e);
}
finally
{
isReauthenticating = false;
semaphore.Release();
}
if (gotNewToken)
{
if (!request.RequestUri.OriginalString.Contains("/.auth/me")) //do not resend in this case since we're not using the return value of auth/me
{
//Resend the request since the user has successfully logged in and return the response
return await ResendRequest(client, request, cancellationToken);
}
}
}
return response;
}
private async Task<HttpResponseMessage> ResendRequest(IMobileServiceClient client, HttpRequestMessage request, CancellationToken cancellationToken)
{
// Clone the request
var clonedRequest = await CloneRequest(request);
// Set the authentication header
clonedRequest.Headers.Remove("X-ZUMO-AUTH");
clonedRequest.Headers.Add("X-ZUMO-AUTH", client.CurrentUser.MobileServiceAuthenticationToken);
// Resend the request
return await base.SendAsync(clonedRequest, cancellationToken);
}
private async Task<bool> RefreshToken(IMobileServiceClient client)
{
var authentication = DependencyService.Get<IAuthentication>();
if (authentication == null)
{
throw new InvalidOperationException("Make sure the ServiceLocator has an instance of IAuthentication");
}
try
{
return await authentication.RefreshUser(client);
}
catch (System.Exception e)
{
Debug.WriteLine("Unable to refresh user: " + e);
}
return false;
}
private async Task<HttpRequestMessage> CloneRequest(HttpRequestMessage request)
{
var result = new HttpRequestMessage(request.Method, request.RequestUri);
foreach (var header in request.Headers)
{
result.Headers.Add(header.Key, header.Value);
}
if (request.Content != null && request.Content.Headers.ContentType != null)
{
var requestBody = await request.Content.ReadAsStringAsync();
var mediaType = request.Content.Headers.ContentType.MediaType;
result.Content = new StringContent(requestBody, Encoding.UTF8, mediaType);
foreach (var header in request.Content.Headers)
{
if (!header.Key.Equals("Content-Type", StringComparison.OrdinalIgnoreCase))
{
result.Content.Headers.Add(header.Key, header.Value);
}
}
}
return result;
}
}
Upvotes: 0
Views: 528
Reputation: 18465
How can I embed these credentials with the MobileServiceClient, so that whenever I call a method it has the correct auth credentials.
Per my understanding, the AuthHandler
class could provide a method for setting the current valid user info after the user has successfully logged in with the correct email and password. Also, you need to cache the AuthHandler
instance which is used to construct the MobileServiceClient
instance, after user logged, you could embed the current user info into the AuthHandler
instance.
If you are talking about providing a sign-in process with a username and password rather than using a social provider, you could just follow Custom Authentication for building your CustomAuthController
to work with App Service Authentication / Authorization (EasyAuth). For your client, you could use the following code for logging:
MobileServiceUser azureUser = await _client.LoginAsync("custom", JObject.FromObject(account));
Moreover, you need to cache the MobileServiceAuthenticationToken
issued by your mobile app backend and manually valid the cached token and check the exp
property of the JWT token under the SendAsync
method of your AuthHandler
class, and explicitly call LoginAsync
with the cached user account for acquiring the new MobileServiceAuthenticationToken
when the current token would be expired soon or has expired without asking the user to log in again. Detailed code sample, you could follow adrian hall's book about Caching Tokens.
Or if you are talking about Basic access authentication, you could also refer the previous part about embedding credentials into your AuthHandler
. For your server-side, you could also add your custom DelegatingHandler
to validate the authorization header and set the related Principal to HttpContext.Current.User
. And you could initialize your DelegatingHandler
under Startup.MobileApp.cs
file as follows:
HttpConfiguration config = new HttpConfiguration();
config.MessageHandlers.Add(new MessageHandlerBasicAuthentication());
Moreover, you could follow Basic Authentication Using Message Handlers In Web API.
Upvotes: 0