h3rald
h3rald

Reputation: 923

Getting a new refresh token with AD FS 4.0 (2016) or higher

I configured AD FS 2016 to support authentication of a "Native Application" via OAuth2/OpenID Connect using Authorization Code Grant with PKCE. I created a relying party and configured (for testing purposes) token lifetimes by setting the following:

Set-AdfsRelyingPartyTrust -TargetName MyRPT -IssueOAuthRefreshTokensTo AllDevices -TokenLifetime 3

Grant-AdfsApplicationPermission -ClientRoleIdentifier MyClient -ServerRoleIdentifier MyRPT -ScopeNames openid,profile,email

Set-AdfsProperty -SSOLifetime 7 -PersistenSsoEnabled $false

...and that gives me Access/ID Tokens that expire after 3 minute and Refresh Tokens that expire after 7 (actually 14, see below) minutes. I also disabled Persistent SSO, so I get no session cookie. All good.

After a successful authentication, my client issues a POST request to the /oauth2/token endpoint, passing the following parameters:

I get a valid response with the following:

{
  "access_token": "...",
  "token_type": "bearer",
  "expires_in": 180,
  "resource": "MyRPT",
  "refresh_token": "...",
  "refresh_token_expires_in": 419,
  "scope": "email profile openid",
  "id_token": "..."
}

Awesome.

Then about 10 seconds before the access token is due to expire, the client issues another POST request to /oauth2/token, this time with the following parameters:

And the following successful response is returned:

{
  "access_token": "...",
  "token_type": "bearer",
  "expires_in": 180,
  "id_token": "..."
}

Note that this time no refresh token is returned. The same happens another FOUR times (about 14 mins in total, so twice the SSOLifetime then?) while the refresh token is still valid, and then finally, at the fourth request for a new access token I get a 400 error with the following body:

{
  "error":"invalid_grant",
  "error_description":"MSIS9615: The refresh token received in \u0027refresh_token\u0027 parameter has expired."
}

Token refresh requests

Which kinda makes sense, but... shouldn't a new refresh token be issued when the current refresh token is close to the expiration time?

The Official Docs are somewhat cryptic on the matter:

Although refresh tokens aren't revoked when used to acquire new access tokens, you are expected to discard the old refresh token. As per the OAuth 2.0 spec says: "The authorization server MAY issue a new refresh token, in which case the client MUST discard the old refresh token and replace it with the new refresh token. The authorization server MAY revoke the old refresh token after issuing a new refresh token to the client." AD FS issues refresh token when the new refresh token lifetime is longer than previous refresh token lifetime. To view additional information on AD FS refresh token lifetimes, visit AD FS Single Sign On Settings.

Err, what? Let me write that in pseudo code:

if (newRefreshTokenLifetime > previousRefreshTokenLifetime) {
  issueNewRefreshToken();
}

...but that would be always then, wouldn't it?

Any idea on how to configure AD FS so that it issues new refresh tokens as well when needed? Ideally it would be nice to have refresh token rotation, but one thing at a time...

Upvotes: 3

Views: 6136

Answers (2)

Bobby Stewart
Bobby Stewart

Reputation: 1

https://github.com/MicrosoftDocs/windowsserverdocs/blob/main/WindowsServerDocs/identity/ad-fs/development/ad-fs-openid-connect-oauth-concepts.md#refresh-token-lifetimes

Simple logon (no KMSI, device not registered): AD FS will apply SsoLifetime + DeviceUsageWindowInDays, and the first refresh token will have lifetime=DeviceUsageWindowInDays or SsoLifetime (based on which field is lower), but no further refresh tokens are issued.

ADFS requires device auth for re-issuing refresh tokens.

Upvotes: 0

Gary Archer
Gary Archer

Reputation: 29218

Usually in OAuth the lifetime of a refresh token is set at the time of the delegation, when the user signs in, at which time they may consent to certain permissions being used for a certain time.

So if a user signs in at 09:00 for an 8 hour session, and their app refreshes an access token at 10:00, if there is a new refresh token issued it should then be usable for 7 hours. That is, you cannot override the initial delegation without involving the user again.

As you say, the newer trend is to get a new refresh token on every access token refresh, but this is just a protection mechanism, and ADFS does not support that. So I would proceed as follows:

  • Set the SSO Lifetime to the desired value, eg 8 hours, and set the access token lifetime to a standard value such as 30 minutes
  • Write code in a future facing way, to discard the existing refresh token if you get a new one

REFRESH TOKENS IN NATIVE APPS

Based on comments, you are trying to use PKCE and would like to use rotating refresh tokens, but ADFS does not support the latter, so you cannot.

You have a native app where the standard solution has always been to store the refresh token in secure operating system storage only available to the app and user. It should not matter whether the refresh token rotates or not. Examples:

SPAs

Tokens and the browser is a whole different topic, since there is nowhere secure to store refresh tokens. With recent third party cookie browser restrictions the only way to get a Javascript app to work is to store refresh tokens in local storage, which is disastrous from a security viewpoint.

The optimal solution to this tricky problem is to use an API driven solution where a utility API issues SameSite=strict cookies for the SPA. There are a few moving parts to deploy to both developer PCs and your pipeline though. See the below Curity resources for details on the design pattern. This will also work fine with ADFS.

Upvotes: 2

Related Questions