Search code examples
securityadfsadfs4.0

ADFS 4.0 (2016) confidential and native client registration for API resource


Given the following on-behalf-of scenario as described in: https://learn.microsoft.com/sv-se/windows-server/identity/ad-fs/development/ad-fs-on-behalf-of-authentication-in-windows-server

ADFS OBO

Question

Is it possible to configure the Middle Tier Service in ADFS 2016 as both a confidential client (Mid-Tier Client acting on-behalf-of) AND a public/native client to be used from swagger ui?

Understanding the sample

For the middle tier service to be able to access the backend webapi on-behalf-of the user we need to register a confidential client (clientid/secret) in ADFS with the ClientID set to the Audience, as stated in the documentation: "It is very important that the ida:Audience and ida:ClientID match each other"

Meaning that the application group now contains a confidential client with the middle tier service URL (audience) set as clientId instead of the guid you would normally expect here, let's say https://localhost:5005 for the sake of this sample.

This is fine, the sample now works, see: https://github.com/ajtowf/WebApp-OpenIDConnect-DotNet for a working sample.

The issue

Let's say I now want to add swagger with swagger-ui to the middle tier service. That would imho be configured in ADFS as a native client since we want to use the implicit flow. (authorization code flow with PKCE is not supported in ADFS 2016)

BUT

If we configure a native client with a guid for swagger, the token will be issued with an audience that can't be used to access the middle tier service itself. (from the looks of it, it can be used to access the user info endpoint: urn:microsoft:userinfo)

To get a valid access_token for the middle tier service the clientid needs to be set to the audience just as in the confidential client case.

BUT

The ClientID needs to be unique within the application group, hence there can only be one registration with the clientid set to the audience.

Is it even possible to have two separate flows/clients configured? Am I missing something?

This seems like a super basic scenario, easily configured with IdentityServer but I'm stuck with ADFS in this case.

ADFS Configuration

Working sample as per documentation for On-Behalf-Of scenario. Working sample as per documentation for On-Behalf-Of scenario.

ClientID set to Audience for Mid Tier Service Client to be able to access Backend WebAPI On-Behalf-Of user. ClientID set to Audience for Mid Tier Service Client to be able to access Backend WebAPI On-Behalf-Of user.

Public client registration for swagger, will result in bad audience due to guid as ClientID instead of resource url. Public client registration for swagger, will result in bad audience due to guid as ClientID instead of resource url.

Can't (obviously) add another application with same ClientID. Can't (obviously) add another application with same ClientID.


Solution

  • Workaround by passing id_token as access_token

    I really don't like this workaround but at least it works, we can get around this problem by using the confidential client as a public client (without the secret).

    Added the oauth2 redirect uri for swagger to ADFS as a valid URI for the server application (I know!!!)

    enter image description here

    And then configuring permissions to allow it to access itself:

    enter image description here

    This allows us to issue an id_token to the swagger client.

    Why not issue an access_token?

    Great question! ;-) A confidential client is not allowed to request an access_token, i.e. response_type=token, but response_type=id_token is OK.

    And if you're using swagger-ui as I am some modifications needs to be done since the response type is hard coded, see;

    But once that is done we can get an id_token with a valid audience that can be used to access our API.

    It's alive

    ClientID as Audience super important: enter image description here

    Gives us a valid id_token with correct audience to access the API: enter image description here

    Not satisfied

    But imho this is a hack, using the id_token as an access_token just feels wrong. And I really struggled with modifying swagger-ui to get it to pass the id_token with the authorization header!

    Please suggest a better way of doing this