Skip to content

Send the scope with client credentials and keep tokens until replaced - #48

Merged
robertlemke merged 3 commits into
mainfrom
task/client-credentials
Sep 14, 2026
Merged

robertlemke merged 3 commits into
mainfrom
task/client-credentials

Conversation

@robertlemke

Copy link
Copy Markdown
Member

Makes the client credentials grant request and store tokens the way applications expect, and adds a migration guide for version 5.0.

requestAccessToken() now sends the requested scope to the token endpoint, unless it is empty. Before, the server never saw the scope and issued its default scope, while the stored authorization claimed the requested one. The server can now restrict the token to what the application asked for, and it rejects scopes which the client may not request. This can break applications, which is why the migration guide explains it in detail. Version 6.0 of flownative/openidconnect-client will no longer add openid to client credentials requests.

An existing token is replaced only after the new token was issued, so a failed request no longer loses a token which is still valid. Tokens without an expiration time get the default token lifetime. Additional parameters which the client sets itself, like grant_type or client_secret, are rejected.

The authorization id no longer includes the client secret and is calculated from a JSON-encoded list, so that different configurations can't share an id. Every application requests its client credentials tokens once more after the update.

Breaking changes: Authorization::generateAuthorizationIdForClientCredentialsGrant() no longer takes the client secret, the scope is sent, and token.defaultLifetime now also applies to client credentials tokens.

Also adds Documentation/Migration-5.0.md, which covers all breaking changes of version 5.0 so far.

requestAccessToken() now sends the requested scope to the token
endpoint, unless it is empty. The previous token is replaced only after
the new one was issued, and tokens without an expiration time get the
default token lifetime. The authorization id no longer includes the
client secret.

Previously, the scope only ended up in the authorization id and in the
database. The authorization server never saw it and issued a token with
its default scope, while the stored authorization claimed the requested
one. Sending the scope lets the server restrict the token to the rights
which the application asked for. If the client may not request the
scope, the server now rejects the request.

The stored authorization was removed before the new token was
requested. If the authorization server was not reachable, a token which
was still valid got lost. The existing authorization is now updated in
place. Because an updated row could keep a token in the storage format
which is no longer used, setting a token clears the other column.

The authorization id was a SHA-512 hash over the concatenated service
name, client id, client secret, scope and parameters. Concatenation let
different configurations share an id, and the hash of the secret was
logged and shown by the command line. The id is now a SHA-256 hash over
a JSON-encoded list without the secret, so every application requests
its tokens once more after the update.

Additional parameters which the client sets itself, like grant_type or
client_secret, are rejected. Tokens without an expiration time were
kept forever. With the default lifetime, they are requested again
regularly.
The guide explains the breaking changes of version 5.0 and what
applications need to check before they upgrade, with the scope of
client credentials requests as the most important point.
The scope column of the authorizations table is now a text column. A
migration for MySQL and PostgreSQL widens it from 255 characters.

Authorizations store the scope which the authorization server granted.
Some servers grant long lists of permissions. Auth0, for example,
returns all permissions of the Management API when a client credentials
request asks for no scope. Such a list exceeds 255 characters, and
storing the token failed with a database error.
@robertlemke
robertlemke merged commit 9a2d3f1 into main Sep 14, 2026
12 checks passed
@robertlemke
robertlemke deleted the task/client-credentials branch September 14, 2026 14:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant