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:
- client_id: "MyClient"
- code: ...
- redirect_uri: ...
- code_verifier: ...
- grant_type: "authorization_code"
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:
- refresh_token: ...
- grant_type: "refresh_token"
- client_id: "MyClient"
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."
}
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...
