keycloak-scim/docs/documentation/server_development/topics/action-token-handler-spi.adoc
Dimitri Papadopoulos Orfanos 9db1443367
Fix typos found by codespell in docs (#28890)
Run `chmod -x` on files that need not be executable.

Signed-off-by: Dimitri Papadopoulos <3234522+DimitriPapadopoulos@users.noreply.github.com>
Signed-off-by: Alexander Schwartz <aschwart@redhat.com>
Co-authored-by: Alexander Schwartz <aschwart@redhat.com>
2024-05-03 12:41:16 +00:00

173 lines
9.1 KiB
Text

[[_action_token_handler_spi]]
== Action Token Handler SPI
An action token is a special instance of Json Web Token (JWT) that permits its bearer to perform some actions, e.g. to
reset a password or validate e-mail address. They are usually sent to users in form of a link that points to an endpoint
processing action tokens for a particular realm.
{project_name} offers four basic token types allowing the bearer to:
* Reset credentials
* Confirm e-mail address
* Execute required action(s)
* Confirm linking of an account with account in external identity provider
In addition to that, it is possible to implement any functionality that initiates or modifies authentication session
using action token handler SPI, details of which are described in the text below.
[[_action_token_anatomy]]
=== Anatomy of action token
Action token is a standard Json Web Token signed with active realm key where the payload contains several fields:
* `typ` - Identification of the action (e.g. `verify-email`)
* `iat` and `exp` - Times of token validity
* `sub` - ID of the user
* `azp` - Client name
* `iss` - Issuer - URL of the issuing realm
* `aud` - Audience - list containing URL of the issuing realm
* `asid` - ID of the authentication session (_optional_)
* `nonce` - Random nonce to guarantee uniqueness of use if the operation can only be executed once (_optional_)
In addition, an action token can contain any number of custom fields serializable into JSON.
=== Action token processing
When an action token is passed to a {project_name} endpoint
`_KEYCLOAK_ROOT_{kc_realms_path}/master/login-actions/action-token` via `key` parameter, it is validated and a proper action
token handler is executed. *The processing always takes place in a context of an authentication session*, either a fresh
one or the action token service joins an existing authentication session (details are described below). The action token
handler can perform actions prescribed by the token (often it alters the authentication session) and results into an HTTP
response (e.g. it can continue in authentication or display an information/error page). These steps are detailed below.
1. *Basic action token validation.* Signature and time validity is checked, and action token handler is determined based
on `typ` field.
2. [[determining-auth-sess]]*Determining authentication session.* If the action token URL was opened in browser with
existing authentication session, and the token contains authentication session ID matching the authentication session
from the browser, action token validation and handling will attach this ongoing authentication session. Otherwise,
action token handler creates a fresh authentication session that replaces any other authentication session present at
that time in the browser.
3. *Token validations specific for token type.* Action token endpoint logic validates that the user (`sub` field) and
client (`azp`) from the token exist, are valid and not disabled. Then it validates all custom validations defined in the
action token handler. Furthermore, token handler can request this token be single-use. Already used tokens would then be
rejected by action token endpoint logic.
4. *Performing the action.* After all these validations, action token handler code is called that performs the actual
action according to parameters in the token.
5. *Invalidation of single-Use tokens.* If the token is set to single-use, once the authentication flow finishes, the
action token is invalidated.
=== Implement your own action token and its handler
==== How to create an action token
As action token is just a signed JWT with few mandatory fields (see <<_action_token_anatomy,Anatomy of action token>>
above), it can be serialized and signed as such using Keycloak's `JWSBuilder` class. This way has been already
implemented in `serialize(session, realm, uriInfo)` method of `org.keycloak.authentication.actiontoken.DefaultActionToken`
and can be leveraged by implementers by using that class for tokens instead of plain `JsonWebToken`.
The following example shows the implementation of a simple action token. Note that the class must have a private constructor without any arguments.
This is necessary to deserialize the token class from JWT.
[source,java]
----
import org.keycloak.authentication.actiontoken.DefaultActionToken;
public class DemoActionToken extends DefaultActionToken {
public static final String TOKEN_TYPE = "my-demo-token";
public DemoActionToken(String userId, int absoluteExpirationInSecs, String compoundAuthenticationSessionId) {
super(userId, TOKEN_TYPE, absoluteExpirationInSecs, null, compoundAuthenticationSessionId);
}
private DemoActionToken() {
// Required to deserialize from JWT
super();
}
}
----
If the action token you are implementing contains any custom fields that should be serializabled to JSON fields, you
should consider implementing a descendant of `org.keycloak.representations.JsonWebToken` class that would implement
`org.keycloak.models.ActionTokenKeyModel` interface. In that case, you can take advantage of the existing
`org.keycloak.authentication.actiontoken.DefaultActionToken` class as it already satisfies both these conditions,
and either use it directly or implement its child, the fields of which can be annotated with appropriate Jackson
annotations, e.g. `com.fasterxml.jackson.annotation.JsonProperty` to serialize them to JSON.
The following example extends the `DemoActionToken` from the previous example with the field `demo-id`:
[source,java]
----
import com.fasterxml.jackson.annotation.JsonProperty;
import org.keycloak.authentication.actiontoken.DefaultActionToken;
public class DemoActionToken extends DefaultActionToken {
public static final String TOKEN_TYPE = "my-demo-token";
private static final String JSON_FIELD_DEMO_ID = "demo-id";
@JsonProperty(value = JSON_FIELD_DEMO_ID)
private String demoId;
public DemoActionToken(String userId, int absoluteExpirationInSecs, String compoundAuthenticationSessionId, String demoId) {
super(userId, TOKEN_TYPE, absoluteExpirationInSecs, null, compoundAuthenticationSessionId);
this.demoId = demoId;
}
private DemoActionToken() {
// you must have this private constructor for deserializer
}
public String getDemoId() {
return demoId;
}
}
----
==== Packaging classes and deployment
To plug your own action token and its handler, you need to implement few interfaces on server side:
* `org.keycloak.authentication.actiontoken.ActionTokenHandler` - actual handler of action token for a particular
action (i.e. for a given value of `typ` token field).
+
The central method in that interface is `handleToken(token, context)` which defines actual operation executed upon
receiving the action token. Usually it is some alteration of authentication session notes but generally it can be
arbitrary. This method is only called if all verifiers (including those defined in `getVerifiers(context)`) have
succeeded, and it is guaranteed that the `token` would be of the class returned by `getTokenClass()` method.
+
To be able to determine whether the action token was issued for the current authentication session as described in
<<determining-auth-sess,Item 2 above>>, method for extracting authentication session ID has to be declared in
`getAuthenticationSessionIdFromToken(token, context)` method. The implementation in `DefaultActionToken` returns the
value of `asid` field from the token if it is defined. Note that you can override that method to return current
authentication session ID regardless of the token - that way you can create tokens that would step into the ongoing
authentication flow before any authentication flow would be started.
+
If the authentication session from the token does not match the current one, the action token handler would be asked to
start a fresh one by calling `startFreshAuthenticationSession(token, context)`. It can throw a `VerificationException`
(or better its more descriptive variant `ExplainedTokenVerificationException`) to signal that would be forbidden.
+
The token handler also determines via method `canUseTokenRepeatedly(token, context)` whether the token would be
invalidated after it is used and authentication completes. Note that if you would have a flow utilizing multiple action
token, only the last token would be invalidated. In that case, you should use
`org.keycloak.models.SingleUseObjectProvider` in action token handler to invalidate the used tokens manually.
+
Default implementation of most of the `ActionTokenHandler` methods is the
`org.keycloak.authentication.actiontoken.AbstractActionTokenHandler` abstract class in `keycloak-services` module. The
only method that needs to be implemented is `handleToken(token, context)` that performs the actual action.
* `org.keycloak.authentication.actiontoken.ActionTokenHandlerFactory` - factory that instantiates action token
handler. Implementations have to override `getId()` to return value that must match precisely the value of `typ`
field in the action token.
+
Note that you have to register the custom `ActionTokenHandlerFactory` implementation as explained in the
<<_providers,Service Provider Interfaces>> section of this guide.