Using AD for authentication between Web app and Web API

In my earlier post about the problem with ACS I promised to blog about how to configure Microsoft Azure Active Directory (MaaD) to support a client app calling a Web API using a client secret (as opposed to presenting an authentication dialogue to an interactive user).

To get started (and save me a lot of typing) I suggest you get started by reading Johan Danforth’s great blog post on how to do this for an interactive login as this is where I started)

If you follow the instructions carefully you will have a Web API published and registered with AD and you will have updated the application’s manifest file in AD to allow you to configure permissions to it in other applications in the directory)

You will also have a client app, also configured in AD (as a ‘native application’) in which you have configured permission to access the Web API and the relevant call to issue the request to AD to get the token and to the Web API bearing that token.

Crucially – you will have written very little code to do this. AD, the ADAL Nuget Package and the OWIN library (on the server side) will have done all the heavy lifting for you.

What I wanted to do is to remove the interactive user from the equation – in my scenario a user uses a web app, which in turn uses my Web API, but this is not a delegation scenario, the web app can call the web api on its own right.

To achieve this, I needed to make two changes from Johan’s example –

Firstly I needed to register my web app as a client app which isn’t a native client as a native client app in AD does not have configuration for client secret. so I’ve added a new app to my directory, made sure to select the ‘web application and/or web api application’ for the type and configured a name, APP ID and (in my case a mock) sign-on url.

I had also set the permission from my web client application to the web-api, in the same way that I did earlier for my native client.

And finally – I added a new key by selecting a duration and copied the secret (important: the key cannot be accessed after its creation, make sure to copy it)

With the AD configuration done I make the relevant changes to the client code to use the client secret rather than the interactive sign on

The original code includes the following line - 

var ar1 = ac.AcquireToken(“https://. . . WEB Api APP ID URI. . .”,
              "e9b5d821-. . .Native Client’s Client Id",
              new Uri("http:// . .  Native Client’s APP ID URI"));

so support authenticating using client secret, this needed to change to

ClientCredential cc = new ClientCredential("e9b5d821-Web Client Client ID",
"Qxc49i. . .client secret generated in AD portal"); var ar1 = ac.AcquireToken(https://. . . WEB Api APP ID URI. . .”, cc);

and with this done (the rest of the code remains unchanged), when I now run the client, rather than being prompt to authenticate the call to the web api is made and authentication is done using the client secret.

In closing, for completeness, the interaction really translates to two web requests – the first is to AD to retrieve the access token, and in my case this looks like this –

POST https://login.windows.net/[1db093b3-…AD tenant ID]/oauth2/token HTTP/1.1

Host: login.windows.net

Content-Type: application/x-www-form-urlencoded

Content-Length: 212

grant_type=client_credentials&client_id=[e9b5d821-…client app client id]&client_secret=[Qxc49iXn…client secret]&resource=[https%3A%2F%2F…..protected resource app ID]

This request, if successful, returns a JSON response which includes a JWT token from AD that can be used in subsequent requests to the application. That subsequent requests looks like this (token trimmed for readability) –

GET http://localhost/WebApplication1/api/Values HTTP/1.1

Host: localhost

Authorization: Bearer eyJ0eXAiOiJKV1QiLCJh……..

Finally – whilst I was experimenting with this I encountered a few errors that are worth noting here –

The one I was most confused by was

"AADSTS90014: The request body must contain the following parameter: ‘client_secret or client_assertion’."

In my case this happened when I tried to request a token from AD through my client app when it was still coded for interactive user sign-on. when the AD application used was native, using the interactive logon code worked fine, as soon as I tried to use a web api AD application without changing the code to use client_secret I got the error above.-

Final thought – ACS is slowly being embedded in AD but for the time being I find the experience a little bit fiddly and short of documentation (in particular the Manifest file manipulation) and lack of flexibility

The integration above currently only works with JWT tokens and client secret authentication (SWT is not supported I believe as is certificate based authentication) but this will be corrected over time, I’m sure.

 

[cross posted on the Solidsoft blog]

Advertisements

The problem with ACS….

…is that it currently has no published SLAs and there’s no out-of-the-box solution for Disaster Recovery in an active scenario (as in – web services, REST services)

Let me expand on these two points –

I could not find any SLA information about ACS, so I asked Microsoft who responded that, as things stand ACS is a free service and they do not offer SLAs for free. This might change as and when ACS is properly rolled into Microsoft Azure Active Directory with its paid, Premium option, but that’s not the case at the moment.

Pragmatically – I have not seen any ACS issues in the past and there are several Azure services that do carry SLA that take a dependency on ACS, namely the Service Bus and BizTalk Services. Make of that what you will – you have to either accept or reject this risk.

To make matters worse – there’s no easy solution for DR for ACS in an active scenario.

Federated identity follows two approaches – when dealing with users and browsers the passive approach is used and that relies on browser redirects. This means that the web app is responsible for redirecting users to the Identity Provider (IdP) and holds the configuration of where the IdP is. That means, for example, a web role deployed across data centres and fronted by Traffic Manager, can have separate configurations to redirect users to different ACS namespaces, depending on the environment.

When dealing with services (SOAP with WS-Federation or REST with oAuth) the service client holds the configuration about where the IdP is (in the SOAP case this is part of the service contract in oAuth it is simply exchanged out of band) and it is the service client’s responsibility to go and get a token from the IdP before calling the service.  This means that if the ACS namespace changes (for example due to outage and switch to DR), the client will need to know to go to the alternative the service won’t tell it automatically.

With this in mind, what are the options?

I can think of 3 or 4. would love to hear additional views –

1. Accept the risk and the need to reconfigure clients when switching to DR.

2. If you have control over the client – implement an operation to automatically (on failure) and/or regularly request the metadata from your web role, which would allow it to auto-recover

3. If either of the above are not acceptable – roll your own oAuth solution on your web role which would mean that client access the same url (managed by traffic manager) to access the service and the IdP.

A better alternative is to use the bit of ACS that is rolled into Active Directory through the configuration of Applications for both the Web API and the client. I’ll post an entry on this shortly. the upside is that if you then make sure your directory is a Premium one you are covered by SLAs, although I’m still not sure what the DR story for AD is.

The main downside of this approach at this point in time is that, currently at least, there’s not much configuration that can be done – it only supports a client secret and JWT tokens with no control over token TTL etc. I also the the process is quite fiddly, requiring tempering with the app manifest manually etc, but I’m sure all of this will improve over time.

[cross posted on the Solidsoft Blog]

Role-based authorisation with Windows Azure Active Directory

Using Window Azure Active Directory (WaaD) to provide authentication for web applications hosted anywhere is dead simple. Indeed in Visual Studio 2013 it is a couple of steps in the project creation wizard.

This provides means of authentication but not authorisation, so what does it take to support authorisation is the [Authorise(Role=xxx)] approach?

Conceptually, what’s needed is means to convert information from claims to role information about the logged-on user.

WaaD supports creating Groups and assigning users to them, which is what’s needed to drive role-based authorisation, the problem is that, somewhat surprising perhaps, group membership is not reflected in the claim-set delivered to the application as part of the ws-federation authentication.

Fortunately, getting around this is very straight forward. Microsoft actually published a good example here but I found it a little bit confusing and wanted to simplify the steps somewhat to explain the process more clearly, hopefully I’ve succeeded –

The process involves extracting the claims principal from the request and, from the provided claims,  find the WaaD tenant.

With that and with prior knowledge of the clientId and key for the tenant (exchanged out of band and kept securely, of course) the WaaD GraphAPI can be used to query the group membership of the user

Finally – the groups can be used to add role claims to the claim-set, which WIF would automatically populate as roles allowing the program to use IsInRole and the [Authorise] attribute as it would normally.

So – how is all of this done? –

The key is to add a ClaimsAuthenticationManager, which will get invoked when an authentication response is detected, and in it perform the steps described.

A slightly simplified (as opposed to better!) version of the sample code is as follows 

 public override ClaimsPrincipal Authenticate(string resourceName, ClaimsPrincipal incomingPrincipal)
        {
            //only act if a principal exists and is authenticated
            if (incomingPrincipal != null && incomingPrincipal.Identity.IsAuthenticated == true)
            {
                //get the Windows Azure Active Directory tenantId
                string tenantId = incomingPrincipal.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;

                // Use the DirectoryDataServiceAuthorizationHelper graph helper API
                // to get a token to access the Windows Azure AD Graph
                string clientId = ConfigurationManager.AppSettings["ida:ClientID"];
                string password = ConfigurationManager.AppSettings["ida:Password"];

                //get a JWT authorisation token for the application from the directory 
                AADJWTToken token = DirectoryDataServiceAuthorizationHelper.GetAuthorizationToken(tenantId, clientId, password);

                // initialize a graphService instance. Use the JWT token acquired in the previous step.
                DirectoryDataService graphService = new DirectoryDataService(tenantId, token);

                // get the user's ObjectId
                String currentUserObjectId = incomingPrincipal.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;

                // Get the User object by querying Windows Azure AD Graph
                User currentUser = graphService.directoryObjects.OfType<User>().Where(it => (it.objectId == currentUserObjectId)).SingleOrDefault();


                // load the memberOf property of the current user
                graphService.LoadProperty(currentUser, "memberOf");
                //read the values of the memberOf property
                List<Group> currentRoles = currentUser.memberOf.OfType<Group>().ToList();

                //take each group the user is a member of and add it as a role
                foreach (Group role in currentRoles)
                {
                    ((ClaimsIdentity)incomingPrincipal.Identity).AddClaim(new Claim(ClaimTypes.Role, role.displayName, ClaimValueTypes.String, "SampleApplication"));
                }
            }
            return base.Authenticate(resourceName, incomingPrincipal);
        }

You can follow the comments to pick up the actions in the code; in broad terms the identity and the tenant id are extracted from the token, the clientid and key are read from the web.config (VS 2013 puts them there automatically, which is very handy!), an authorisation token is retrieved to support calls to the graph API and the graph service is then used to query the user and its group membership from WaaD before converting, in this case, all groups to role claims.

To use the graph API I used the Graph API helper source code as pointed out here. in Visual Studio 2013 I updated the references to Microsoft.Data.Services.Client and Microsoft.Data.OData to 5.6.0.0.

Finally, to plug in my ClaimsAuthenticationManager to the WIF pipeline I added this bit of configuration –

  <system.identityModel>
    <identityConfiguration>
          <claimsAuthenticationManager
type="WebApplication5.GraphClaimsAuthenticationManager,WebApplication5" />

With this done the ClaimsAuthenticationManager kicks in after the authentication and injects the role claims, WIF’s default behaviour then does its magic and in my controller I can use, for example –

        [Authorize(Roles="Readers")]
        public ActionResult About()
        {
            ViewBag.Message = "Your application description page.";

            return View();
        }

Cross posted on the Solidsoft blog

%d bloggers like this: