Adding OAuth2 to ADFS (and thus bridging the gap between modern Applications and Enterprise Back ends)

AuthorizationServer can be combined with arbitrary authentication methods, but the fact that it comes pre-configured as a WS-Federation relying party, makes it particularly easy to combine it with e.g. ADFS.

This is a really interesting scenario, because it essentially allows adding OAuth2 support to your enterprise authentication infrastructure. And this in turn allows connecting your “modern applications” like HTML/JS, native mobile apps (iOS, Android, Win8/WP8) very easily to your enterprise Web API back ends.

That’s exactly what quite a high percentage of my customers want *today* – e.g. connecting their iPads to a Web API in their AD protected back end. Unfortunately this is not possible with *today’s* available Microsoft products.

AuthorizationServer to the rescue! By adding AS as a relying party to your ADFS you can leverage your existing federated authentication system – and that can range from “simple” IdP-only scenarios to full blown infrastructures containing federation gateways, proxies and (external) federation partners.

Here and here I already introduced the WS-Federation configuration for AuthorizationServer – this post specifically deals with the AS/ADFS combination.

Add AuthorizationServer as a relying party to ADFS
The first step is to “register” AS in ADFS. This is a normal relying party registration. Go to Trust Relationships –> Add Relying Party Trust and select Enter data manually.

Choose some display name, e.g. “AuthorizationServer” and don’t select an encryption certificate. Then enable WS-Federation and add the base address of your AS installation as the protocol URL:

image

On the next screen you have to choose a relying party identifier. That’s up to you, but AS comes pre-configured with urn:authorizationserver. So that’s probably the easiest:

image

On the issuance authorization rules page you could now configure which domain users get access to AS and thus OAuth2. That’s very nice but I leave that for you as an exercise if needed. By default every domain user will be able to request tokens.

As the last step, you have to add a claim rule that passes the user name to AS. You could use the domain\user format or user@domain. That’s up to you.

Use a pass-through rule for that:

image

You could also pass other arbitrary claims to AS, but the minimum requirement is a unique user identifier (this will become the sub claim in the access token).

That’s it. One last things since you are already in the ADFS management tool. Later on we need the thumbprint of the ADFS signing certificate. You can get the from Service –> Certificates. Double click the token-signing cert and copy the thumbprint (under Details):

image

Gotcha: This dialog is a buggy piece of sh**. You wouldn’t believe how often I already pulled my hair because of some “mysterious behaviour”. When you copy from this dialog you also copy some invisible Unicode characters. That means when pasting the thumbprint into web.config, the certificate validation will fail, because the APIs search for the value plus the hidden characters – even when string comparison succeeds. Can drive you crazy. The workaround is to use a text editor like Notepad+ or Sublime to remove the hidden characters (also remove the spaces for good measure)….or just use this great tool from Raffaele: DeployManager.

Connect ADFS to AuthorizationServer
Next you need to connect AS to ADFS. This could be done using the Identity & Access VS Add-in (see the video I linked to above). I am almost always faster changing the configuration manually.

First open configuration\identityModel.services.config in the WebHost project:

<system.identityModel.services>

  <federationConfiguration>

   

    <wsFederation passiveRedirectEnabled=true

                  issuer=https://adfs_server/adfs/ls

                  realm=urn:authorizationserver />

 

  </federationConfiguration>

</system.identityModel.services>

 

If you also used urn:authorizationserver in ADFS then you only need to insert the name of the ADFS server here.

Next open identityModel.config:

<system.identityModel>
  <identityConfiguration>
    
    <audienceUris>
      <add value="urn:authorizationserver" />
    </audienceUris>
    
    <issuerNameRegistry type=" …ValidatingIssuerNameRegistry, … ">
      <authority name="ADFS">
        <keys>
          <add thumbprint="signing_cert_thumbprint" />
        </keys>
        <validIssuers>
          <add name="http://adfs_server/adfs/services/trust" />
        </validIssuers>
      </authority>
    </issuerNameRegistry>
 
  </identityConfiguration>
</system.identityModel>

 

Here you need to paste in the token signing cert thumbprint and add your ADFS issuer URI. This is typically the above value modulo your ADFS server name. If you are unsure, you can look that up in the ADFS MMC under Federation Service Properties.

If you want to use the OAuth2 resource owner credential flow, you also need to open autofac.config to configured the OAuth2/WS-Trust bridge:

<autofac>
  <components>
    <component type="WSTrustResourceOwnerCredentialValidation"
               service="IResourceOwnerCredentialValidation" >
      <parameters>
        <parameter name="address"
                   value="https://adfs_server/adfs/services/trust/13/usernamemixed" />
        <parameter name="realm"
                   value="urn:authorizationserver" />
        <parameter name="issuerThumbprint"
                   value="signing_cert_thumbprint" />
      </parameters>
    </component>
  </components>
</autofac>

 

Here you need to configure ADFS’ WS-Trust endpoint (again, typically inserting the server name is sufficient) and also the signing cert thumbprint again.

Now AS is a proper ADFS relying party. The last step would be configure which domain user(s) are AS administrators. You have two options here: either run the initial configuration wizard and enter the user name there, or open the AS configuration database in App_Data and enter the user name into the AuthorizationServerAdministrators table directly.

Make sure you use the same user name format as you configured in ADFS (SAM account name format vs UPN). By default that would be domain\user.

To verify everything is working properly, try the Flows demo project and try out every OAuth2 flow. If everything is fine – you are now able to access AS and the Web API using your domain account – and that’s exactly what we wanted to achieve.

What about ADFS3?
The version of ADFS on soon to be released Windows Server 2012 R2 has built-in OAuth2 support. So you might not need our AuthorizationServer at all. In fact, many companies/admins indeed prefer a supported Microsoft product compared to some OSS project – also the the fact that it is built-into ADFS as opposed to a separate server piece might be easier to manage. ADFS3 also has other features around BYOD that could be useful to you. So if you can deploy Windows Server 2012 R2 this is a viable option.

Nevertheless I still see AuthorizationServer as very valuable and in a number of situations actually far more useful as ADFS OAuth2 support, e.g.

  • AS implements all OAuth2 flows – and ADFS3 only code flow (I might be wrong on that one since there is no real documentation right now). But we see a lot of value in the other flows as well – so do our customers.
  • AS fully embraces the OAuth2 scope concept – as well as the full authorization model around it (client, scopes, flows etc..). ADFS3 seems to divide its world into resources (== relying parties) and even uses a non-standard protocol parameter for that. There does not seem to be support for scopes at all. We had the same issue when we tried to add OAuth2 to IdentityServer which is a classic IdP – the concepts just don’t mix very well. That’s why we built AS from scratch.
  • For many real world scenarios it is actually beneficial that AS and ADFS are separate pieces. ADFS is typically administrated by “Domain Admins” whereas the authorization server logically belongs to the application and thus to “Application Admins”. I’ve seen a lot of friction between those two parties. Separating the concerns makes them often happier.
  • AS allows for full customization, e.g. the consent screen. ADFS deliberately moved away from IIS to limit customization.
  • AS has a UI for OAuth2. ADFS has PowerShell. sigh.

So to summarize – ADFS in general is a powerful identity provider/federation gateway for Active Directory based networks and user bases. ADFS3 adds “limited” OAuth2 capabilities to it.
AuthorizationServer is a fully featured implementation of OAuth2 – and in combination with ADFS as the authentication back end you get the best of both worlds.

This entry was posted in AuthorizationServer, IdentityModel, IdentityServer, OAuth, WebAPI. Bookmark the permalink.

20 Responses to Adding OAuth2 to ADFS (and thus bridging the gap between modern Applications and Enterprise Back ends)

  1. two comments, one suggestion.

    I agree on ADFS v3 – its concept is one of device-management. it doenst – in this version – envision OAUTH as a internet-apps framework. But, that’s likely to change, as the tie of Azure AD to app management (of sharepoint, exchange, etc) breaks down. Just like ADCS, the “ISP-scale” cert minting version of ADCA is not well-published. it does exist that is, and Im sure the ISP-scape OAUTH server is part of ADFSs vision.

    Our own path to mix ws-fedp SPs and OAUTH was very similar to that which you describe. We found it possible, however, to merge the OAUTH endpoints with the IDP, buit that’s only because our IDP happened to have a SP-builtin – powered by its own session management. Thus, we really extended the SP (powered by websso) to expose AS-like features. As it stands we have not bothered with consent screens, or dialogies for grant management. We just think of OAUTH as yet another blob/protocol format, needing gatewaying.

    The BEST thing one could do with AS is make an Azure image, that one can just drop into a Azufe hosted VM. Then, Id play more. As it stands, Its hard to use – requiring such buyin to its coding practices (as excellent as they are), or its membership dbs, etc, or its config style. And those are hard to adopt, being (naturally) alien. Experience private-cloud vendors don’t just mix and match any old system-integratedservers, each with a different concept; making a IT staff mess (or the kind of mess that RSA is famous for delivering).

  2. Amazing! This is what I was looking for!

  3. Daniel says:

    A really helpful tutorial. I had figured most of this out, except for the config changes needed to support Resource Owner flow.

    I did run into problems when calling OAuth2Client.RequestAccessTokenUserName(), getting the error “The underlying connection was closed: Could not establish trust relationship for the SSL/TLS secure channel.”, which apparently was caused because of the certificate used on the AS server. By using a self-signed certificate with “Issued by” and “Issued to” given the same values as the name of the machine running AS, I managed to install that certificate (using Internet Explorer) on the machine running this code, and the error went away.

    However, now I’m getting the error “Response status code does not indicate success: 500 (Internal Server Error).” and have absolutely no idea what might cause that. Could this be a problem with the certificate used on the machine running ADFS?

    • check out the trace logs in AS (see tracing.config).

      • danielbsig says:

        Thanks, this lead me to my error (failing to use https when configuring autofac).

        Another question – unrelated: are there any downsides to providing a Login method within a Web API project (using Resource Owner flow), instead of having a separate web application just for that? We would like to use a pure HTML/CSS/JS SPA application, but won’t be using any backend for the client. This will probably be very similar to what Christian Weyer is doing here: https://github.com/ChristianWeyer/ngMasterDetail but it would be interesting to hear your opinion on this.

      • With “any backend” you mean a separate authorization backend and embed the resource owner endpoint inside the “business service” instead?

        We think that’s OK as long as the client is already using the token request/toke use semantics. This way it is easy to separate the RO endpoint if that need arises. Just don’t store the user’s password inside the SPA – but use the access token/refresh token instead ;)

      • danielbsig says:

        Yes, the username/password combo will be thrown away as soon as possible.

        Right now I’ve got two questions:
        a) after having called RequestAccessTokenUserName(), what is the best way to get information out of the token? I assume there is some class within the IdentityModel which is capable of this?

        b) I’m also having CORS issues – Chrome is sending an OPTIONS request for resources which are marked as [Authorize], but for some reason it isn’t passing the Authentication header along with it. I’m not even sure what the question really is :-), I’ll have to look more into this. (We’ve already enabled CORS in WebApiConfig.cs).

        Daníel

  4. a) you would call an endpoint in your system – sending the token – and this endpoint would return the user information
    b) CORS requests are processed way before a controller gets hit. So this is unrelated to the Authorization header.

    • danielbsig says:

      I was hoping I could skip the round-trip. I.e. my client calls a method in my API, this method calls RequestAccessTokenUserName(), and returns the AccessToken back. What I wanted to achieve is to let that method return other information as well, so that the client doesn’t have to issue two requests where one should be enough.

  5. Well – you could extend AS to return more information as part of the token response (in clear text). You could also parse the token directly. It is JSON (base64 encoded).

    But the token is not designed to contain useful information for the client, rather for the backend it is issued for. So my approach is the “right” one ;)

    • danielbsig says:

      Yes, I won’t be messing with the token per se. My method will return an object containing the token, plus other information, quite similar to what Christian Weyer did in his example, i.e. information about what options to display in the UI, based on what the user can and cannot do.

      • danielbsig says:

        Just to answer myself: to get the data from the token this can be used:

        JwtSecurityToken token = new JwtSecurityToken(tokenResponse.AccessToken);
        Debug.WriteLine(token.Subject);

        Hopefully I can answer the second question as well later :-)

  6. Andy Duclos says:

    I am having some issues, when you say the thumbprint of the ADFS certificate I am assuming this is the service communications certificate. In addition you say to paste the thumbprint modulo the ADFS server name so I assume this is “thumbprint\servername”. When I get to the login prompt returned from ADFS it just keeps prompting for username and password. I have seen this with kerberos before is there something I am missing? My servers are distributed ADFS on one and AuthorizationServer on another. Any help is appreciated.

    • No I mean the signing certificate thumbprint – doesn’t the text say that?

      Not sure what the Kerberos issue is.

      • I’d find this much more compelling if ACS was the wsfedp partner, not adfs. Then adfs, azure ad, and openid would all be driving the as.

        if a new external nameid is encountered, as pops up the local login dialog, as part of consent, storing the externals to local mapping as part of the consent record.

  7. IK says:

    How can we configure oAuth 2.0 in ADFS 3.0.?

  8. Pingback: Integrating AuthorizationServer with Auth0 | leastprivilege.com

  9. Lars Riewe says:

    Hi,
    is it possible to use adal/adfs3.0 with oauth2-endpoint to get a edge token for web application proxy that uses preauthentication to publish a WCF Service in the BackEnd? We have a WPF client, but we never get a valid edge token after successful login, we only get some access token that’s missing relevant claims for the WAP to accept. Is ADAL/OAuth2/ADFS3.0/WAP only working for Windows Store Apps, or are we missing something?

Leave a comment