Grants¶
A grant is a way of obtaining tokens at the token endpoint, POST /token (RFC 6749 §3.2). The provider
serves seven, and a client uses the ones it registered in grant_types and no other.
grant_type |
What the client presents | Who the tokens are about | Page |
|---|---|---|---|
authorization_code |
The code a browser brought back to it | The person who signed in and agreed | Authorization code |
refresh_token |
A refresh token it was issued | The person of the authorization it renews | Refresh token |
client_credentials |
Its own authentication, and nothing else | Nobody: the client itself | Client credentials |
urn:ietf:params:oauth:grant-type:device_code |
The device code it is polling with | The person who confirmed the code on another device | Device code |
urn:openid:params:grant-type:ciba |
The auth_req_id of a backchannel request |
The person who approved it on a page of the provider | Backchannel authentication |
urn:ietf:params:oauth:grant-type:token-exchange |
An access token of this provider | Whoever that token is about | Token exchange |
urn:ietf:params:oauth:grant-type:jwt-bearer |
An assertion signed by an issuer the tenant trusts | The person the assertion names | JWT bearer |
No class serves the resource owner password credentials grant, and the authorization endpoint answers with a
code and nothing else, so there is no implicit flow either: a grant_type of password is answered
unsupported_grant_type, as every name the provider does not serve is. Secure by
default says why neither exists.
What the token endpoint does¶
The endpoint takes a POST of form parameters and answers in JSON, a success as an error, with
Cache-Control: no-store and Pragma: no-cache. It reads a request in this order and stops at the first
refusal:
-
The client. It authenticates with the one method it registered (client authentication). A request whose authentication is malformed is
invalid_request: credentials that cannot be read, two authentication mechanisms in one request, or aclient_idthat is not the one the credentials name. A client that does not prove who it is, or proves it with another method than the one it registered, isinvalid_client. -
What the request binds its tokens to (RFC 8705 §3, RFC 9449 §5):
- the key of the DPoP proof the request carries;
- the certificate of the TLS connection, for a client that authenticates with
tls_client_authorself_signed_tls_client_auth, or that registeredtls_client_certificate_bound_access_tokens. A client that authenticates with a secret or an assertion and registered no such binding gets bearer tokens, whatever certificate its connection presented; - and nothing else: a request that would bind both a certificate and a key is refused with
invalid_dpop_proof.
A client registered with
require_sender_constrained_tokenswhose request binds nothing, and a client registered withtls_client_certificate_bound_access_tokenswhose request presents no certificate, are refused withinvalid_requestbefore anything the request came to spend is read. -
The grant type.
grant_typeis looked up among the grants the provider is wired with, and a name none of them gives itself isunsupported_grant_type. -
The registration. A client that did not register that grant is
unauthorized_client. -
The grant, whose refusals are the errors of RFC 6749 §5.2 and of the specification it implements.
Of those errors, invalid_client is answered 401 and every other one 400. A request refused because a
load budget is spent, that of the address it comes from or that of the client once it has authenticated, is
answered 429 with a Retry-After header and an error_description, and no error member.
Sender-constrained tokens says what a binding changes for the client that
holds the tokens.
The answer¶
| Member | When it is there |
|---|---|
access_token |
Always |
token_type |
Always: DPoP for a token bound to a key, Bearer otherwise, a token bound to a certificate included (RFC 8705 §3) |
expires_in |
Always, the seconds the access token lives |
issued_token_type |
In the answer of a token exchange (RFC 8693 §2.2.1), and of no other grant |
refresh_token |
When the grant issued one |
id_token |
When openid is among the scopes of the answer and a person who signed in at this provider is behind it |
scope |
Always, the scopes the access token carries |
authorization_details |
When the authorization carries details beyond its scopes (RFC 9396 §7) |
What each grant answers with¶
| Grant | ID token | Refresh token | A public client |
|---|---|---|---|
| Authorization code | With openid |
When one is due | Served |
| Refresh token | With openid |
Its successor, when the token rotates | Served |
| Client credentials | Never, openid being refused |
Never | Refused at registration |
| Device code | With openid |
When one is due | Served |
| Backchannel authentication | Always, openid being required |
When one is due | Served |
| Token exchange | Never | Never | Its request fails: no token is issued |
| JWT bearer | Never, openid being refused |
Never | Its request fails: no token is issued |
A refresh token is due when the client is registered for refresh_token and either offline_access is
among the scopes or the client is confidential. Refresh token says what spending one
does.
What a registration carries¶
A client declared in the configuration that names no grant_types is registered for authorization_code
and refresh_token; a client that registers itself and names none is registered for authorization_code
alone, which is what RFC 7591 §2 gives it. What a client names is read against the grants the provider is
wired with, and against what each of them does:
- A name no wired grant gives itself is refused.
refresh_tokenneeds, beside it, a grant that issues refresh tokens:authorization_code, the device code grant or backchannel authentication.authorization_codeneeds at least one redirect URI. A client registered for no grant that sends a browser back to it registers no redirect URI and noresponse_types: a redirect URI nothing would ever use is refused.client_credentialsneeds an authentication method that proves something, sononeis refused, and aclient_idthat can be asub: one to 255 visible ASCII characters, and no space.- Token exchange needs a
token_exchangedeclaration, and atoken_exchangedeclaration needs the grant. Theclient_idis held to the same rule as forclient_credentials.
A declared client that breaks one of these rules is an error of the configuration, raised with the name of
the client as soon as the clients are read. A registration request that breaks one is answered
invalid_redirect_uri for the two rules on redirect URIs and invalid_client_metadata for the others.
One more condition is read at the endpoint and not at registration: a client of backchannel authentication
is served only once it registered a backchannel_token_delivery_mode, a member with no default. A client
registered for the grant without one is accepted, and refused at the backchannel authentication endpoint
with unauthorized_client. Clients has every other member of a registration.
What is announced¶
grant_types_supported, in the discovery document, is the list of the wired
grants in the order they are wired, which is the order of the table at the top of this page. The members
that go with a grant follow it: the backchannel authentication endpoint and its metadata are announced only
while a grant of that flow is wired, and offline_access is offered only while a wired grant issues refresh
tokens and another spends them.
Adding a grant¶
A grant is a class implementing Beffroi\Oidc\Application\Token\GrantInterface, tagged
beffroi.oidc.grant. It names its grant_type, and turns an authenticated client and the parameters of a
request into tokens or into an error of RFC 6749 §5.2. The token endpoint, the reading of a registration and
the discovery document read the one tagged list, so a grant added to the wiring is served, registrable and
announced, and a grant removed from it is none of the three. Two grants that give themselves the same name
are refused, and so is a token endpoint wired with none.
What a grant does beyond that is declared by the interfaces it implements beside GrantInterface, in the same
namespace, and never by a rule written about its name:
| Interface | What the grant declares | What it changes |
|---|---|---|
IssuesRefreshTokensInterface |
Its answer may carry a refresh token | The rule on refresh_token, and the offer of offline_access |
SpendsRefreshTokensInterface |
It spends a refresh token | Whether a refresh token is due, the rule on refresh_token, and the offer of offline_access |
SendsTheUserToTheClientInterface |
Its flow sends a browser back to the client | The rule on redirect URIs and response types |
ActsForTheClientItselfInterface |
Nobody is signed in behind it | The rule on public clients and on the client_id |
AsksTheUserOnAnotherDeviceInterface |
Its flow starts at the device authorization endpoint | That endpoint, which refuses a client of no such grant |
AsksTheUserThroughABackchannelInterface |
Its flow starts at the backchannel authentication endpoint | That endpoint, its metadata, and the audiences a client assertion may name |
ExchangesATokenInterface |
It takes a token and gives one back | The rule on token_exchange |