diff --git a/server_development/SUMMARY.adoc b/server_development/SUMMARY.adoc index 5c26c94e3b..947d75bf29 100755 --- a/server_development/SUMMARY.adoc +++ b/server_development/SUMMARY.adoc @@ -11,6 +11,7 @@ .. link:server_development/topics/providers.adoc[Service Provider Interfaces (SPI)] .. link:server_development/topics/extensions.adoc[Extending Server] .. link:server_development/topics/auth-spi.adoc[Authentication SPI] + .. link:server_development/topics/action-token-spi.adoc[Action Token SPI] .. link:server_development/topics/events.adoc[Event Listener SPI] {% endif %} .. link:server_development/topics/user-storage.adoc[User Storage SPI] diff --git a/server_development/topics/action-token-spi.adoc b/server_development/topics/action-token-spi.adoc new file mode 100644 index 0000000000..75822b4696 --- /dev/null +++ b/server_development/topics/action-token-spi.adoc @@ -0,0 +1,118 @@ +[[_action_token_spi]] +== Action Token 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. + +{{book.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 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 {{book.project.name}} endpoint +`_KEYCLOAK_ROOT_/auth/realms/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 a 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 implementors by using that class for tokens instead of plain `JsonWebToken`. + +==== 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 +<>, 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.ActionTokenStoreProvider` in action token handler to invalidate the used tokens manually. ++ +Default implementation of most of the `ActionTokenHandler` methods is the +`org.keycloak.authentication.actiontoken.AbstractActionTokenHander` 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. + + * 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. + +