Using Proof Key for Code Exchange (PKCE) in ADFS for Windows Server 2019

This is the first in a series on PKCE.

Active Directory Federation Services (ADFS) now supports PKCE in Server 2019.

As per the documentation:

“OAuth public clients using the Authorization Code Grant are susceptible to the authorization code interception attack. The attack is well described in RFC 7636. To mitigate this attack, AD FS in Server 2019 supports Proof Key for Code Exchange (PKCE) for OAuth Authorization Code Grant flow.

To leverage the PKCE support, this specification adds additional parameters to the OAuth 2.0 Authorization and Access Token Requests.

A. The client creates and records a secret named the “code_verifier” and derives a transformed version “t(code_verifier)” (referred to as the “code_challenge”), which is sent in the OAuth 2.0 Authorization Request along with the transformation method “t_m”.

B. The Authorization Endpoint responds as usual but records “t(code_verifier)” and the transformation method.

C. The client then sends the authorization code in the Access Token Request as usual but includes the “code_verifier” secret generated at (A).

D. The AD FS transforms “code_verifier” and compares it to “t(code_verifier)” from (B). Access is denied if they are not equal.”

In detail as per this:

  1. Authentication: The client generates a high-entropy random string called code_verifier
  2. Authentication: The client generates a hash from the code_verifier called code_challenge
  3. Authentication: The client sends the authorization request containing the code_challenge, the method used to hash it, along with the rest of params to ADFS
  4. Authentication: User signs in with their ADFS identity
  5. Authentication: If the user’s sign in was successful, ADFS returns the code to the client
  6. Authorization: The client then sends the code together with the code_verifier to the token endpoint
  7. Authorization: Before returning the access_token, ADFS recomputes the hash using the code_verifier and the hashing method used by the client and compares it with the one sent initially during the authentication request
  8. If they match, the client then receives the access_token

In terms of the hashing, the PKCE specification defines two methods, S256 and plain.

I’ve used “S256” i.e. SHA256 and you should always use this. (See the footnote for plain.)

Technically, you create a random string (the code_verifier), hash it with SHA256 and then Base 64 URL encode this to get the code_challenge.

In terms of the code to do this, I couldn’t find a .NET C# example that worked but I ended deriving the code from this post for .NET Core.

The code is here in a gist.

In practice, you should use a random string but I used a fixed one just to make debugging easier.

//var codeVerifier = CryptoRandom.CreateUniqueId(32);
var codeVerifier = “1qaz2wsx3edc4rfv5tgb6yhn1234567890qwertyuiop”;

Also note that ADFS needs a longer string than 32 characters.

You get:

MSIS9497: The code_verifier length of 32 is not supported.

Running this code produces:

codeVerifier 1qaz2wsx3edc4rfv5tgb6yhn1234567890qwertyuiop

codeChallenge _r67lcj4MoDNBAkhxS7ke_YKhKCBAiM0SgzNCagbCxo

In ADFS, we need to use authorisation code grant so that’s a web browser accessing a web application as an application group.

This gives e.g.

We just need the “Client ID” and a “Redirect URI”. I’m going to use Postman so the “Redirect URI” is really a dummy entry for redirection but in practice, this would be for your application.

The GET to the /authorize endpoint is:

https://my-adfs/adfs/oauth2/authorize?

response_type=code

&client_id=89d59605-a5f5-4b98-9dbb-86749661e0e5

&scope=openid

&redirect_uri=https://jwt.io

&prompt=login

&code_challenge=_r67lcj4MoDNBAkhxS7ke_YKhKCBAiM0SgzNCagbCxo

&code_challenge_method=S256

This needs to be run in a browser.

Note the “client_id” and the “redirect_uri” are as configured in ADFS and the “code_challenge” is as per the C# code above.

This will take you to the ADFS login page.

Authenticate and ADFS will return a URL with:

https://jwt.io/?code=RT5V…NmpQ

The POST to the /token endpoint is:

The “code” is as returned in the first GET request and the “code_verifier” is as per the C# code above.

If you haven’t calculated the “code_challenge” correctly, you get:

{
“error”: “invalid_grant”,
“error_description”: “MSIS9720: Unable to validate code_verifier.”
}

and you need to look in the event log to get more details.

Once it’s all working, you get:

Success!

Footnote:

The PKCE specification states:

If the client is capable of using "S256", it MUST use "S256", as
"S256" is Mandatory To Implement (MTI) on the server. Clients are
permitted to use "plain" only if they cannot support "S256" for some
technical reason and know via out-of-band configuration that the
server supports "plain".

ADFS does accept “plain”.

plain
code_challenge = code_verifier

Here the “code_challenge” and “code_verifier” are the same.

The differences in the GET are:

https://my-adfs/adfs/oauth2/authorize?response_type=code&

code_challenge=1qaz2wsx3edc4rfv5tgb6yhn1234567890qwertyuiop&code_challenge_method=plain

and it then works the same as the above.

All good!

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store