Refresh token lifecycle in OpenAM could be very tricky, not easy to understand and implement. So that we share in this document our experiences.
1. Description
Refresh Token is a feature provided with Oauth2 Authorization code or Resource Owner Password Credentials Grant. The refresh token has long time life cycle, whereas the access token is very short.
Typically a refresh token can be valid for over a year, whereas an access token is only valid usually 10 minutes. For the both Oauth2 stream mentioned above, using a valid refresh token allows to request a new access token on demand.
OpenAM provides such a feature, since it has introduced Oauth2 support (openam version 11). An Oauth2/openID connect Provider has to be created/configured on OpenAM server first to allow Oauth2 client request type.
2. unnecessary refresh token generated
Till openAM 13.5, the default behavior was to have the option « Refresh Tokens on Refreshing Access Tokens » enabled.
The way to access to this option is: Access Control -> realm (used) -> service -> Oauth2Provider
Having this option selected had several (nasty) side effects:
2.1) Once a refresh token has been used, it can no longer be re-used.
Any further invocation of the refresh token once used will trigger a
message:
{« error_description »: »grant is invalid », »error »: »invalid_grant »}
2.2) Stale refresh token are piling up, and are not recycled, as only the last refresh token can be used.
This contributes to make the embedded ldap memory size growing, and can be subject to DDOS attacks if nothing is done to delete those stale refresh token.
2.3) Each time a new refresh token is issued, a new expiry date is provided.
« expireTime » : « Oct 4, 2017 5:21 PM » (1st refresh token issuance) « expireTime » : « Oct 4, 2017 5:45 PM » (2nd refresh token issuance) As a consequence, it means that the expiry date is always moving forward upon refresh token request/issuance.
3. Unchecking option « Refresh Tokens on Refreshing Access Tokens » benefits
Unchecking this option in openAM provided the following benefits:
- it is possible to reuse the same refresh token (as long as the resfresh is valid) .
- There is no stale resfresh token left, as there is always only the same refresh token available.
- Refresh token expiry date does not move forward/is not sliding, as always the same token is used.
Hence it is possible to maintain a fixed reference date for refresh token expiry.
4. Refresh token example usage
In this section is shown Refresh Token example usage (after having unchecked option « Refresh Tokens on Refreshing Access Tokens »
4.1) Generate access and Refresh token
sh generate_demo_oauth2_token.sh AQIC5wM2LY4SfczIhgGbELpc9vwsl2U1abPG0F05N3X0dQk.*AAJTSQACMDEAAlNLABMxODk4NDUzOTIyNTk0MTU4NDU5*
curl -X POST \
-u « myClientID:oauthclient » \
-d « grant_type=password » \
-d « username=demo » \
-d « password=changeit » | json_pp
+ json_pp
+ curl -X POST -u myClientID:oauthclient -d grant_type=password -d username=demo -d password=changeit
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 219 0 168 100 51 92 28 0:00:01 0:00:01 –:–:– 92
{
« token_type » : « Bearer »,
« scope » : « profile »,
« access_token » : « 6b670d3b-6f9d-404a-9a33-c464ffc66960 »,
« refresh_token » : « 265a600e-79a2-4af8-b39e-0a0afdf1dff3 »,
« expires_in » : 8399
}
4.2) Generate access token from refresh token
script used:
generate_oauth2_access_token_from_refresh_token.sh
curl -X POST \
-u myClientID:oauthclient \
-d « grant_type=refresh_token » \
-d « refresh_token=$1 »
4.2.1) sh generate_oauth2_access_token_from_refresh_token.sh 265a600e-79a2-4af8-b39e-0a0afdf1dff3
{« access_token »: »6039f566-b71e-45da-b7a6-7b9d985f4d9a », »scope »: »profile », »token_type »: »Bearer », »expires_in »:8399}
—> No refresh token created.
One day after, using same refresh token as yeterday
4.2.2) sh generate_oauth2_access_token_from_refresh_token.sh 265a600e-79a2-4af8-b39e-0a0afdf1dff3
{« access_token »: »643e08c4-e926-4309-bbd6-20ff95408381″, »scope »: »profile », »token_type »: »Bearer », »expires_in »:8399}
—-> possible to reuse same refresh token
—–> new access token generated
—–> as only one refresh token which remain valid
5) Verification
The verification requires to have a valid SSO session
5.1) Generating OpenAM SSO token
generate_demo_sso_token.sh
curl -X POST -H « X-OpenAM-Username: demo » -H « X-OpenAM-Password: changeit » -H « Content-Type: application/json » -d » \
\
| json_pp
sh generate_demo_sso_token.sh
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 143 100 143 0 0 47 0 0:00:03 0:00:02
0:00:01 47
{
« successUrl » : « /openam/console »,
« tokenId » :
« AQIC5wM2LY4SfczFt6eAx85P91bxgMX17wSo-Nzw3geynB8.*AAJTSQACMDEAAlNLABQtMTYyMDgxNzY2OTc5NTA3MjU3NQ..* »
}
5.2) List all refresh token (only 1 here)
sh list_oauth2_refresh_token.sh AQIC5wM2LY4SfcyQEDuvtY8cfTJDkQDgAFEj8N_OFf0fLPo.*AAJTSQACMDEAAlNLABQtMzA1NTMwMTU1MjUyNTQwNjkzNw..*
curl \
–header « iplanetDirectoryPro: $1 » \
\
?_queryId=refresh_token | json_pp
+ json_pp
+ curl –header iplanetDirectoryPro: AQIC5wM2LY4SfcyQEDuvtY8cfTJDkQDgAFEj8N_OFf0fLPo.*AAJTSQACMDEAAlNLABQtMzA1NTMwMTU1MjUyNTQwNjkzNw..*
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 786 100 786 0 0 547 0 0:00:01 0:00:01 –:–:– 547
{
« remainingPagedResults » : -1,
« pagedResultsCookie » : null,
« resultCount » : 2,
« result » : [
{
« redirectURI » : [],
« scopes » : « profile »,
« expireTime » : « Oct 10, 2017 10:02 PM »,
« clientID » : [
« myClientID »
],
« authModules » : [],
« id » : [
« 265a600e-79a2-4af8-b39e-0a0afdf1dff3 »
],
« tokenName » : [
« refresh_token »
],
« scope » : [
« profile »
],
« grant_type » : [
« password »
],
« userName » : [
« demo »
],
« display_name » : « »,
« tokenType » : [
« Bearer »
],
« realm » : [
« / »
],
« acr » : []
},
{
« display_name » : « »,
« userName » : [
« demo »
],
« refreshToken » : [
« 265a600e-79a2-4af8-b39e-0a0afdf1dff3 »
],
« realm » : [
« / »
],
« tokenType » : [
« Bearer »
],
« parent » : [],
« scopes » : « profile »,
« expireTime » : « Oct 10, 2017 10:02 PM »,
« clientID » : [
« myClientID »
],
« redirectURI » : [],
« grant_type » : [
« refresh_token »
],
« scope » : [
« profile »
],
« nonce » : [],
« id » : [
« 643e08c4-e926-4309-bbd6-20ff95408381 »
],
« tokenName » : [
« access_token »
]
}
]
}
6) Conclusion
Having unchecked this option (Refresh Tokens on Refreshing Access Tokens) is fulfilling exactly the behavior that we are aiming to:
- Reuse of the same refresh token along a very period of time
- Only one refresh token used
- New Keycloak online training - 19 janvier 2022
- Sizing Keycloak or Redhat SSO projects - 8 juin 2021
- Keycloak.X Distribution - 28 janvier 2021