Azure Mobile Apps (formerly known as Azure Mobile Services) provide a great cloud based framework for rapid development of mobile applications (which also could be used to develop web applications, when needed). Azure Mobile Apps empower developer with a tools set (both client-side and server-side) helping to tackle on common mobile application development tasks, like:
- Authentication
- Accessing data on the server from a mobile device
- Push notifications
- Exposing secure APIs which are easily consumable from mobile devices
Speaking of authentication, Azure Mobile Apps provide an easy way for a developer to set up user authentication through most popular identity providers like Facebook, Google, Microsoft Live and Active Directory. All that developer need is to configure authentication settings in the application and Azure Mobile Apps framework will take care of the rest.
But what if you need to customize your authentication process? For example, you may need to add some custom claims to authenticated user principal, like application role for example. Having a role stored among user claims would help with authorizing user, because not all users are created equal. This is certainly possible, but requires some coding and the process is not well documented. Let me outline the required steps here.
If you created your Azure Mobile App backend from a quick start template or followed Microsoft’s article on creating Azure Mobile App backend, then you should have a code somewhere in your application which looks like this:
public partial class Startup { public static void ConfigureMobileApp(IAppBuilder app) { HttpConfiguration config = new HttpConfiguration(); //For more information on Web API tracing, see http://go.microsoft.com/fwlink/?LinkId=620686 config.EnableSystemDiagnosticsTracing(); new MobileAppConfiguration() .UseDefaultConfiguration() .ApplyTo(config); MobileAppSettingsDictionary settings = config.GetMobileAppSettingsProvider().GetMobileAppSettings(); if (string.IsNullOrEmpty(settings.HostName)) { // This middleware is intended to be used locally for debugging. By default, HostName will // only have a value when running in an App Service application. app.UseAppServiceAuthentication(new AppServiceAuthenticationOptions { SigningKey = ConfigurationManager.AppSettings["SigningKey"], ValidAudiences = new[] { ConfigurationManager.AppSettings["ValidAudience"] }, ValidIssuers = new[] { ConfigurationManager.AppSettings["ValidIssuer"] }, TokenHandler = config.GetAppServiceTokenHandler() }); } app.UseWebApi(config); } }
The code above basically does the following:
- Configures features in your mobile application backend (by default its caling UseDefaultConfiguration() to enable all the features).
- Checks if application if running under Azure App Service.
- If application is not running in Azure (it’s running locally), then configure authentication to use preset authentication keys and default token handler. This step is not needed when application is running in Azure because Azure Mobile Apps takes care of that, but it’s needed in order to make authentication working in local debug environment.
There is a great blog post describing how to set app settings parameters for item #3 there: http://www.systemsabuse.com/2015/12/04/local-debugging-with-user-authentication-of-an-azure-mobile-app-service/ I’m not planing to duplicate that.
This is a line which is interesting us the most:
TokenHandler = config.GetAppServiceTokenHandler()
In this line we telling Azure Mobile App framework to use default token handler. Generally, we would not need to do that, because it’s a default token handler. But we need to provide other authentication settings to SDK, so we have to provide a (default) token handler as well, because it’s a part of AppServiceAuthenticationOptions.
Token handler is an object which is taking care of validating authentication token and creating ClaimsPrincipal, to which we would want to inject our custom claims (like application roles). Source code for default token handler could be found there: https://github.com/Azure/azure-mobile-apps-net-server/blob/master/src/Microsoft.Azure.Mobile.Server.Authentication/AppServiceTokenHandler.cs As you can see from the source code, token validation is performed by TryValidateLoginToken method which is a, likely, virtual. Which means we can subclass AppServiceTokenHandler and override TryValidateLoginToken. TryValidateLoginToken is called every time when ClaimsPrincipal needs to be created from authentication token, so it’s a great place to add our custom claims. Let’s do it!
public class AppServiceTokenHandlerWithCustomClaims : AppServiceTokenHandler { public AppServiceTokenHandlerWithCustomClaims(HttpConfiguration config) : base(config) { } public override bool TryValidateLoginToken( string token, string signingKey, IEnumerable<string> validAudiences, IEnumerable<string> validIssuers, out ClaimsPrincipal claimsPrincipal) { var validated = base.TryValidateLoginToken(token, signingKey, validAudiences, validIssuers, out claimsPrincipal); if(validated) { // this is your custom role provider class which would lookup user roles by user id var myRoleProvider = new MyRoleProvider(); // get user id (sid) string sid = claimsPrincipal.With(u => u.FindFirst(ClaimTypes.NameIdentifier)).With(u => u.Value); // get user roles (from database, for example) var roles = myRoleProvider.GetUserRolesBySid(sid); foreach(var role in roles) { ((ClaimsIdentity)claimsPrincipal.Identity).AddClaim(new Claim(ClaimTypes.Role, role)); } } return validated; } }
Now, let’s inject our custom token handler to Azure Mobile App framework:
if (string.IsNullOrEmpty(settings.HostName)) { // This middleware is intended to be used locally for debugging. By default, HostName will // only have a value when running in an App Service application. app.UseAppServiceAuthentication(new AppServiceAuthenticationOptions { SigningKey = ConfigurationManager.AppSettings["SigningKey"], ValidAudiences = new[] { ConfigurationManager.AppSettings["ValidAudience"] }, ValidIssuers = new[] { ConfigurationManager.AppSettings["ValidIssuer"] }, TokenHandler = new AppServiceTokenHandlerWithCustomClaims(config) }); }
Well, but that only takes care of local debug part, right? What’s about the case when our code is running in Azure App Service? That is a valid concern. We need to inject our token handler in both scenarios.
Azure Mobile Apps SDK contains an extension which should do just that – inject token handler without providing any other settings. That’s AuthenticationHttpConfigurationExtensions.SetAppServiceTokenHandler. However, I wasn’t able get this extension to work, I guess I was missing something in the process. So I resorted to to way which is working: UseAppServiceAuthentication extension method. But unlike our case with local debug environment, we don’t want to provide authentication settings from config file, we want to get them from Azure environment, because we have them there.
// check if we running in app service or locally if (string.IsNullOrEmpty(settings.HostName)) { // This middleware is intended to be used locally for debugging. By default, HostName will // only have a value when running in an App Service application. app.UseAppServiceAuthentication(new AppServiceAuthenticationOptions { SigningKey = ConfigurationManager.AppSettings["SigningKey"], ValidAudiences = new[] { ConfigurationManager.AppSettings["ValidAudience"] }, ValidIssuers = new[] { ConfigurationManager.AppSettings["ValidIssuer"] }, TokenHandler = new AppServiceTokenHandlerWithCustomClaims(config) }); } else { // we are in app service // ideally, we just need to inject our own AppServicetokenHandler // but when we do that we also have to provide other settings. // SetAppServiceTokenHandler extension doesn't seem to be working. var signingKey = GetSigningKey(); string hostName = GetHostName(settings); app.UseAppServiceAuthentication(new AppServiceAuthenticationOptions { SigningKey = signingKey, ValidAudiences = new[] { hostName }, ValidIssuers = new[] { hostName }, TokenHandler = new AppServiceTokenHandlerWithCustomClaims(config) }); }
And that’s how we going to get these settings:
private static string GetSigningKey() { // Check for the App Service Auth environment variable WEBSITE_AUTH_SIGNING_KEY, // which holds the signing key on the server. If it's not there, check for a SigningKey // app setting, which can be used for local debugging. string key = Environment.GetEnvironmentVariable("WEBSITE_AUTH_SIGNING_KEY"); if (string.IsNullOrWhiteSpace(key)) { key = ConfigurationManager.AppSettings["SigningKey"]; } return key; } private static string GetHostName(MobileAppSettingsDictionary settings) { return string.Format("https://{0}/", settings.HostName); } }
(I’m assuming that we authenticating the user over HTTPS, which is a recommended security practice).
Well, this is it. We now how our custom role claim is authenticated user claim set. And if we would like to validate if user is in role then we could derive our custom authorization attribute from AuthorizeAttribute and have the following code in OnAuthrization:
string userRole = Identity.FindFirstValue(ClaimTypes.Role); if (requiredRoles.Any(c => c == userRole)) base.OnAuthorization(context); else HandleUnauthorizedRequest(context);