OAuth 2.0: Refresh token grant flow

The Refresh Token grant type is used by clients to exchange a refresh token for an access token when the access token has expired. You can get refresh tokens only for the OAuth 2.0: Authorization code flow.

App (Client)
    │
    │  1. Authenticate with client ID, secret, and refresh token
    │     to request a new access token
    ├──────────────────────▶ Apaleo Identity API (Authorization Server)
    │
    │  2. Provide an access token
    │◀────────────────────── Apaleo Identity API (Authorization Server)
    │
    │  3. Authenticate with the access token to request data
    │     from the resource server
    ├──────────────────────▶ Apaleo Resource APIs (Resource Server)
    │
    │  4. Provide the requested data
    │◀────────────────────── Apaleo Resource APIs (Resource Server)
    ▼

New OAuth2 access tokens have expirations. Tokens return an expires_in field indicating how long the token will last.

A refresh token is a special kind of token that contains the information required to obtain a new access token or ID token. Usually, you will need a new access token only after the previous one expires.

Client requests a new token

The refresh token grant allows your app to ask Apaleo to issue a new access_token directly, without re-authenticating the user. This will work as long as the refresh token has not been revoked and, for fine-grained clients, the user who connected the app is still eligible (see Refresh can fail if the user is no longer eligible).

Refresh tokens are subject to strict storage requirements to ensure that they are not leaked.

To refresh your token, using the refresh_token you already got during authorization, make a POST request to the https://identity.apaleo.com/connect/token endpoint in the authentication API, using grant_type=refresh_token.

POST https://identity.apaleo.com/connect/token
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token&client_id=SDXE-AC-MYAPP&client_secret=ZiEWFgFeSP......5yhTgsZgx&refresh_token=_A4CpoIDT.......qYEpxEW8g
Request Value
Method POST
URL Uses the OAuth endpoint https://identity.apaleo.com/connect/token.
Header Value
Authorization Basic authorization, using the client id and client secret of the OAuth client.
Content-Type application/x-www-form-urlencoded

Example cURL request

You can test the creation of token by running the following cURL command.

On Windows, use Windows Subsystem for Linux (WSL) to run the cURL command.

curl -X POST \
  https://identity.apaleo.com/connect/token \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  --data  client_id=SDXE-AC-MYAPP \
  --data  client_secret=ZiEWFgFeSP......5yhTgsZgx \
  --data  grant_type=refresh_token \
  --data  refresh_token=E042F......5EC95
Key Description
client_id The ID assigned to your app when it was registered.
client_secret Your client application’s secret.
grant_type The value refresh_token.
refresh_token The refresh_token that you already got when you exchanged authorization code for token in the OAuth 2.0: Authorization code flow.

Refresh can fail if the user is no longer eligible

With a fine-grained client, the identity server checks the connecting user’s eligibility every time you refresh a token and on every API call, not only when the user first connects your app. A user is eligible only if their role and property access cover all the permissions your app requires. If the user’s Apaleo role or property access no longer covers everything your app was granted, the refresh fails with 400 Bad Request, even though the refresh_token itself hasn’t expired or been revoked:

{
  "error": "User {userName} is no longer eligible for client {clientId} on account {accountCode}"
}

When you get this response, your app has lost access for that hotel:

  • Stop using the stored refresh_token for that connection, and mark the connection as disconnected in your app.
  • Ask the hotel to connect your app again. The connecting user’s role and property access must cover everything your app requests.
  • Don’t show the error message to end users as it is, because it contains the user name and the account code.

If refresh calls that used to succeed start failing with this response, check whether the connecting user’s role or property access changed before assuming it’s an integration bug.

This is different from API calls that return 403 Forbidden. A 403 means that your app lacks the permission an endpoint requires, or hasn’t been granted access to the property the request is about. Your access token itself is still valid.

Authorization server grants a new token

After verifying the request, the identity server sends a response with a new access token to the connected app.

The response will include a new access_token, its type, its lifetime (in seconds), and the id_token.

{
  "id_token": "eyJ...b9w",
  "access_token": "eyJ...Vsg",
  "expires_in": 3600,
  "token_type": "Bearer",
  "refresh_token": "E042F......5EC95",
  "scope": "openid profile availability.read rates.read reservations.read identity:account-users.read offline_access"
}

The example above is for a scope client. For a fine-grained client, scope contains the marker scope identity:permissions and the OpenID Connect scopes (for example openid profile offline_access), but no API scopes. The app’s permissions are still in effect and are enforced separately. Don’t infer API access from this field; check the client’s configured permissions instead.

You should only ask for a new token if the access_token has expired, or you want to refresh the claims contained in the id_token. Calling the endpoint to get a new access_token every time you call an API works, but we wouldn’t call it the best practice.

During implementation or debugging, you might want to check the contents of those token, for example, to read the account code. Here’s one handy tool jwt.io. They write “[…] We do not record tokens, all validation and debugging is done on the client side”, and it should be okay to use them, but - better safe than sorry. We recommend to be cautious, and not post the production tokens of real customers there.