How do I setup a valid on-premise ADFS URI?

Peter picture Peter · Oct 10, 2016 · Viewed 8.4k times · Source

I have a .NET 4.6.2 Windows client application which needs to get an authentication token from our on-premise ADFS server and use it to call an ASP.NET Core REST API. It's client name, id (GUID) and re-direct URI have been registered with ADFS. I am using the latest ADAL (v3.13) library to facilitate the authentication. I am attempting to get a token as demonstrated in the ADAL sample code like this:

AuthenticationContext authenticationContext = new AuthenticationContext("https://<adfs-sts-server>/<rest-api-host>", false);
var result = authenticationContext.AcquireTokenAsync(<rest-api-resource-uri>, clientId, redirectUri, new PlatformParameters(PromptBehavior.Auto));

The AcquireTokenAsync call returns an error, saying: The browser based authentication dialog failed to complete. Reason: The server has not found anything matching the requested URI (Uniform Resource Identifier).

Can anyone tell me:

  1. Is the "requested URI" refered to in the error the https://<adfs-sts-server>/<rest-api-host> or <rest-api-resource-uri>?
  2. Do I need to register <rest-api-host> or <rest-api-resource-uri> with ADFS in some way, and if so how?
  3. Any other information I need to get this to work?

Thanks! Peter

Answer

Peter picture Peter · Oct 19, 2016

Using Active Directory Federation Services (ADFS) to provide authentication for on-premise endpoints from a Windows Client

Configuring ADFS

There are 2 parts to configuring ADFS.

Register the client application with ADFS

ADFS needs to be able to identify the application requesting user authentication, whether it be a service, WPF application, Web client or Office Add-in. I have gone generic and added the following client, which we can use for most of our C# requests; we may need to register a new client with different callback for Web clients.

Use one of the many tools out there to generate a GUID for the client ID.
* CLIENT_ID and APP_NAME should be unique. * For a web client the redirect URI is where the auth service will redirect your call after authenticating the user. It should be an endpoint where you can process the token and continue with your client application. The redirect URI is not really used with rich clients/services/add-ins.

CLIENT_ID = 26E54EC9-7988-4DAE-A527-483A8A78B1C6
APP_NAME = Investplus
DESCRIPTION = Invest+ rich client suite
REDIRECT_URI = https://server/redirect-adfs.html
Instructions for Client registration

(may be possible in a wizard, but this is what I found on the web and it worked fo us)

  1. Log on to the AD FS server as administrator and open a Windows PowerShell command window.
  2. Enter the following command. In Windows PowerShell

    Add-AdfsClient -ClientId <CLIENT_ID> -Name <APP_NAME> -RedirectUri <REDIRECT_URI>

Register the resource to be accessed ('Relying Party' in ADFS speak)

I found this link useful, it takes you through the steps of the wizard for setting up a relying party.

Instructions for Relying Party registration

The administrator on the server team will need to use the ADFS Add Relying Party Trust Wizard, and under the "Select Data Source" step select Enter data about the relying party manually.

Values you need to supply for this wizard:

DISPLAY_NAME                                      = "MyInvestApi" (Unique display name for this Relying party)
PROFILE                                           = "AD FS Profile"
ENABLE_SUPPORT_FOR_WS-FEDERATION_PASSIVE_PROTOCOL = true
URL                                               = "https://server/api"  (Unique URL for this RP)
ADD_ONE_OR_MORE_IDENTIFIERS                       = eg. "urn:myInvestApi" and "https://server/api"
ACCEPT_REMAINING_DEFAULTS

when given the opportunity, Add Claim Rules:

SEND_LDAP_ATTRIBUTES_AS_CLAIMS = true
ATTRIBUTE_STORE                = Active Directory
SELECT_USEFUL_ATTRIBUTES       = User-Principal-Name; Email; Display-Name

Configuring/Coding the Client application

Microsoft provides Active Directory Authentication Libraries (ADAL) for a range of platforms and languages from C# to Javascript, and from iOS to Cordova to Node.

The API exposed has changed significantly in each major version: I am using the latest C# library, currently 3.13.5.

The library makes the coding very simple, just a few lines; where I had problems was:

  1. I couldn't find an explanation of what URL to use for the ADFS Secure Token Service (STS)
  2. I couldn't find documentation of the whole process as I am doing here (most documentation focussed on Azure FS), I struggled to work out how the values provided to ADFS for Client and Relying party mapped to the values used in the code.

What is the ADFS endpoint/URL to use in code?

Microsoft's best practice is to name your ADFS/STS server URL https://sts.domain.com (some people use https://adfs.domain.com, ask your server admins). However, if you try to hit this from a browser you'll get a 404 - Not found and trying to retrieve a token in the code, the ADAL library reports:

 The browser based authentication dialog failed to complete. Reason: The server has not found anything matching the requested URI (Uniform Resource Identifier).

This is how I found the endpoint to use:

  1. ADFS pubishes federation metadata at 'https://sts.domain.com/federationmetadata/2007-06/federationmetadata.xml'
    • Extract this file and open in a text editor.
  2. When configuring the Relying Party, we specified "Enable Support for WS-Federation Passive Protocol" when specifying our resource endpoint, so search the XML for PassiveRequestorEndpoint.
  3. Use the <Address> from this node - in my case https://sts.domain.com/adfs/ls/. I don't know if this will always be the value, or if it is specified when ADFS is setup and therefore potentially different per site.

What other values to use in the code?

We want our client app to retrieve a JSON Web Token (JWT) from ADFS which we can pass to our protected resource for authentication/authorization purposes.

At its most simple, the access token can be retrieved in 3 lines of code + configuration, and this will show how to translate what we have configured in ADFS to the values required by ADAL:

var stsEndpoint = "https://sts.domain.com/adfs/ls/";
var relyingPartyIdentifier = "urn:myInvestApi";    // Tenant in Azure AD speak, but this is an on-premise service
var authority = stsEndpoint + relyingPartyIdentifier;
var restResourceUrl = "https://server/api";   
var redirectUri = "https://server/redirect-adfs.html";
const string CLIENT_ID = "26E54EC9-7988-4DAE-A527-483A8A78B1C6";

AuthenticationContext authenticationContext = new AuthenticationContext(authority, false);
var asyncRequest = authenticationContext.AcquireTokenAsync(restResourceUrl, CLIENT_ID, redirectUri, new PlatformParameters(PromptBehavior.Auto));
var accessToken = asyncRequest.Result.AccessToken;

Useful references