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;
}
}
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 theAuthHandler
instance which is used to construct theMobileServiceClient
instance, after user logged, you could embed the current user info into theAuthHandler
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:Moreover, you need to cache the
MobileServiceAuthenticationToken
issued by your mobile app backend and manually valid the cached token and check theexp
property of the JWT token under theSendAsync
method of yourAuthHandler
class, and explicitly callLoginAsync
with the cached user account for acquiring the newMobileServiceAuthenticationToken
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 customDelegatingHandler
to validate the authorization header and set the related Principal toHttpContext.Current.User
. And you could initialize yourDelegatingHandler
underStartup.MobileApp.cs
file as follows:Moreover, you could follow Basic Authentication Using Message Handlers In Web API.