In this article we share examples of offline token usage in Keycloak.

As mentioned previously, it is possible to generate offline either through direct access grant or authorization code flow.

Both way are going to be illustrated in this chapter

Using offline Token through direct access grant flow
Requirement

The requirement is to have:

  • a client application deployed within a realm
  • a user created in this realm, who has got off_line role
Token lifespan

For the example, token lifespan has been adjusted as follows:

  • SSO session Idele Timeout: 1mn
    (a.k.a Refresh token validity is 1minute)
  • Access token: 1 min
  • OffLine Tokens: 60 days
Examples of offline token usage in Keycloak
Setting the maximum invokation of refresh token

It is possible to the maximum number amount of times a refresh token can be reused, before being ineffective

This is done using:

  • The revoke refresh token toggle
  • indicates the maximum number of times a refresh token can be reused
Examples of offline token usage in Keycloak

If limit was to be reached, following error message would be issued:

{
  "error": "invalid_grant",
  "error_description": "Maximum allowed refresh token reuse exceeded"
}
Script used to offline token
set -xv

refresh_token=`curl \
-d "client_id=ldap-app" -d "client_secret=password" \
-d "username=user1" -d "password=password" \
-d "grant_type=password"  \
-d "scope=openid info offline_access"  \
https://localhost:8080/auth/realms/ldap-demo/protocol/openid-connect/token  | jq -r '.refresh_token'`

#iter 2

curl \
-d "client_id=ldap-app" -d "client_secret=password" \
-d "grant_type=refresh_token" \
https://localhost:8080/auth/realms/ldap-demo/protocol/openid-connect/token   \
 -d "refresh_token=$refresh_token" | jq

sleep 200

#iter 3
curl \
-d "client_id=ldap-app" -d "client_secret=password" \
-d "grant_type=refresh_token" \
https://localhost:8080/auth/realms/ldap-demo/protocol/openid-connect/token   \
 -d "refresh_token=$refresh_token" | jq

sleep 200

#iter 4

curl \
-d "client_id=ldap-app" -d "client_secret=password" \
-d "grant_type=refresh_token" \
https://localhost:8080/auth/realms/ldap-demo/protocol/openid-connect/token   \
 -d "refresh_token=$refresh_token" | jq
Explanation of the script

Part 1

refresh_token=`curl \
-d "client_id=ldap-app" -d "client_secret=password" \
-d "username=user1" -d "password=password" \
-d "grant_type=password"  \
-d "scope=openid info offline_access"  \
https://localhost:8080/auth/realms/ldap-demo/protocol/openid-connect/token  | jq -r '.refresh_token'`

Within this part:

  • The user is connecting to keycloak through direct access grant flow
  • the request contains scope=openid info offline_access
    • This allows to generate an refresh tokenof type offline
  • The refresh token is extracted from the request

The refresh token issued from such a request is JWT token, and has got
« typ »: « Offline ».
(For normal refresh token, the typ is “Refresh”)

{
  "jti": "261f8a49-d252-442d-8375-9485a755a116",
  "exp": 0,
  "nbf": 0,
  "iat": 1550059086,
  "iss": "https://localhost:8080/auth/realms/ldap-demo",
  "aud": "https://localhost:8080/auth/realms/ldap-demo",
  "sub": "c1e44f9e-311d-4a8b-9ce8-c69249e24fd5",
  "typ": "Offline",
  "azp": "ldap-app",
  "auth_time": 0,
  "session_state": "ab9a8db9-9220-43bf-905a-402313c69c5a",
  "realm_access": {
    "roles": [
      "offline_access"
    ]
  },
  "scope": "openid email info offline_access profile"
}

Part 2

The script is using the refresh token generated in first step

  • It is reusing the same refresh token
  • Each time time, a new access token is issued
#iter 2

curl \
-d "client_id=ldap-app" -d "client_secret=password" \
-d "grant_type=refresh_token" \
https://localhost:8080/auth/realms/ldap-demo/protocol/openid-connect/token   \
 -d "refresh_token=$refresh_token" | jq

sleep 200

#iter 3
curl \
-d "client_id=ldap-app" -d "client_secret=password" \
-d "grant_type=refresh_token" \
https://localhost:8080/auth/realms/ldap-demo/protocol/openid-connect/token   \
 -d "refresh_token=$refresh_token" | jq
……
……
Revoking the offline token

The revokation of the offline token can be done in 2 places:

  • Through the admin console
  • By the the user himself
Revokation of the offline token through the admin UI
Examples of offline token usage in Keycloak

The admin user has select the “Revoke” action to revoke the offline token.

Through the user self service panel

The user access to the self-service panel, from where he can revoke the grant “offline access”, as action

Examples of offline token usage in Keycloak
Necessity of adding offline in client request scope

Request without client scope

Normal refresh token request
refresh_token=`curl \
-d "client_id=ldap-app" -d "client_secret=password" \
-d "username=user1" -d "password=password" \
-d "grant_type=password"  \
-d "scope=openid info"  \
https://localhost:8080/auth/realms/ldap-demo/protocol/openid-connect/token  | jq -r '.refresh_token'`

TO be noticed:

  • The payload of the refresh token is of type “Refresh”
  • The “exp” (expiry date) is 60s larger than the “iat” (issuance time)

{
  "jti": "b5653d56-614e-424e-9e15-5351f47d73d4",
  "exp": 1550059512,
  "nbf": 0,
  "iat": 1550059452,
  "iss": "https://localhost:8080/auth/realms/ldap-demo",
  "aud": "https://localhost:8080/auth/realms/ldap-demo",
  "sub": "c1e44f9e-311d-4a8b-9ce8-c69249e24fd5",
  "typ": "Refresh",
  "azp": "ldap-app",
  "auth_time": 0,
  "session_state": "e3ac603e-03c9-44e6-a75c-6841812f88d2",
  "scope": "openid email info profile"
}
Request with client scope

Resuest with scope=offline to request an offline token

refresh_token=`curl \
-d "client_id=ldap-app" -d "client_secret=password" \
-d "username=user1" -d "password=password" \
-d "grant_type=password"  \
-d "scope=openid info offline_access"  \
https://localhost:8080/auth/realms/ldap-demo/protocol/openid-connect/token  | jq -r '.refresh_token'`

TO be noticed:

  • The payload of the refresh token is of type “OffLine”
  • The “exp” (expiry date) is 0
{
  "jti": "261f8a49-d252-442d-8375-9485a755a116",
  "exp": 0,
  "nbf": 0,
  "iat": 1550059086,
  "iss": "https://localhost:8080/auth/realms/ldap-demo",
  "aud": "https://localhost:8080/auth/realms/ldap-demo",
  "sub": "c1e44f9e-311d-4a8b-9ce8-c69249e24fd5",
  "typ": "Offline",
  "azp": "ldap-app",
  "auth_time": 0,
  "session_state": "ab9a8db9-9220-43bf-905a-402313c69c5a",
  "realm_access": {
    "roles": [
      "offline_access"
    ]
  },
  "scope": "openid email info offline_access profile"
}
Keycloak offline example

Keycloak provides an offline demo example to showcase, how it is possible to used offline tokens with Java performing authorization code flow.

The example is available at:

Step1 – User needs to log to the app – An offline access token is generated
Examples of offline token usage in Keycloak
Step 2 – user logs out from app

The off line access token is still valid

step3 – the app can access to the resources using the offline access token
Examples of offline token usage in Keycloak
janua
Les derniers articles par janua (tout voir)