Token Procedure Plugin#
Plugin Descriptor interface:
TokenProcedurePluginDescriptor
Token Procedure Plugins provide implementations for one or more token procedure types
(e.g. AssistedTokenProcedure, AuthorizeCodeTokenProcedure etc.).
Not all types need to be provided. Return null from
any of the getter methods in this interface if the token procedure returned by
the getter is not implemented by this plugin (this is what the default
implementation of all getters does).
If a type is not provided, it is a configuration error to attempt to use such token procedure in a configuration.
JavaScript Token Procedures#
It is possible to implement TokenProcedure without creating a full plugin by writing simple
Script Procedures , which are written in JavaScript.
This is more convenient and faster to develop and may suffice in many cases.
However, for more complex and testable solutions, writing a Token Procedure Plugin may be a better choice.
Developing Token Procedure Plugins#
A token procedure plugin is an SDK plugin and the Curity Plugins SDK contains documentation with an overview on how the plugin system works. For reference information of the Java types, use Plugin SDK Javadocs.
A token procedure plugin is described by a descriptor class, implementing the TokenProcedurePluginDescriptor interface, and overriding a get method for each token procedure type that is supported by the plugin. For instance, a token procedure plugin providing a token procedure usable on the authorization endpoint for the authorization code flow, must:
- Contain a class implementing the
AuthorizeCodeTokenProcedureinterface. - In the descriptor class, override the
getOAuthAuthorizeEndpointCodeTokenProceduremethod so that it returns theClassinstance for the class implementing theAuthorizeCodeTokenProcedureinterface.
A token procedure provided by a plugin is a class implementing one of the base interfaces (e.g.AuthorizeCodeTokenProcedure), containing a single run method, receiving a context and returning a ResponseModel. The returned response model is used by Curity Identity Server to build the endpoint response. The context, which has a different type per procedure type, provides data and services required to produce the endpoints response map, namely issuing tokens.
The following class exemplifies a token procedure for the authorization code flow of the token endpoint. Notice the use of default data and token issuers, provided by the context, to create the different tokens required for the response.
Example token procedure for the authorization code flow of the token endpoint, written using the Kotlin language.
class ExampleAuthorizationCodeTokenProcedure(
private val config: ExampleTokenProcedurePluginConfiguration,
) : AuthorizationCodeTokenProcedure {
override fun run(context: AuthorizationCodeTokenProcedurePluginContext) {
try {
val defaultDelegationData = context.defaultDelegationData
val delegation = context.delegationIssuer.issue(defaultDelegationData)
val defaultAccessTokenData = context.defaultAccessTokenData
val accessToken = context.accessTokenIssuer.issue(defaultAccessTokenData, delegation)
?: throw TokenIssuerException("AccessToken must always be issued, but wasn't.")
val refreshToken = context.refreshTokenIssuer.issue(context.defaultRefreshTokenData, delegation)
val idToken = if (context is OpenIdConnectAuthorizationCodeTokenProcedurePluginContext) {
var idTokenData = context.defaultIdTokenData
if (idTokenData == null) {
null
} else {
idTokenData = IdTokenAttributes.of(idTokenData.with(
Attribute.of(
"at_hash", context.idTokenIssuer.atHash(accessToken)
)
))
context.idTokenIssuer.issue(idTokenData, delegation)
}
} else {
null
}
val result = mutableMapOf<String, Any>(
"access_token" to accessToken,
"refresh_token" to refreshToken,
"scope" to defaultAccessTokenData.scope,
"token_type" to "bearer",
"expires_in" to Duration.between(Instant.now(), defaultAccessTokenData.expires).seconds,
)
if (idToken != null) {
result["id_token"] = idToken
}
if (context.accessTokenClaimNames != null) {
result["claims"] = context.accessTokenClaimNames
}
return ResponseModel.mapResponseModel(result)
} catch (ex: TokenIssuerException) {
throw config.getExceptionFactory().internalServerException(ErrorCode.TOKEN_ISSUANCE_ERROR, ex.message)
}
}
}
The reference documentation for the TokenProcedure, present in the Plugin SDK Javadocs, contains information about all the available procedure types, as well as the context classes that they may receive.
Using Custom Token Issuers#
It’s possible to define custom token issuers and then use them in token procedure plugins. The steps below highlight what needs to be done.
- Add a getter for the issuer to the plugin’s configuration interface.
Example token procedure plugin configuration with a getter for a custom access token issuer.
interface TokenProcedurePluginConfiguration : Configuration {
fun getMyIssuer(): AccessTokenIssuer
}
- Use the custom token issuer in the token procedure plugin (via the configuration getter).
Example of using a custom access token issuer in a token procedure plugin.
class CustomClientCredentialsTokenProcedure(
private val config: TokenProcedurePluginConfiguration,
) : ClientCredentialsTokenProcedure {
override fun run(context: ClientCredentialsTokenProcedurePluginContext): ResponseModel {
val delegation = context.delegationIssuer.issue(context.defaultDelegationData)
val customAccessTokenIssuer = config.getMyIssuer()
val accessToken = customAccessTokenIssuer.issue(context.defaultAccessTokenData, delegation)
return ResponseModel.mapResponseModel(mapOf("access_token" to accessToken))
}
}
- Configure a custom access token issuer in the server configuration under a token profile.
Example of a configuration for a custom access token issuer.
<custom-token-issuer>
<id>CustomAccessTokenIssuer</id>
<issuer-type>opaque</issuer-type>
<purpose-type>access_token</purpose-type>
<data-sources>
<tokens-data-source-id>DefaultHSQLDB</tokens-data-source-id>
</data-sources>
</custom-token-issuer>
See Custom Token Issuers for more information about custom token issuers configuration.
- Configure the custom token issuer for a token procedure plugin instance.
Configuring a custom token issuer in a plugin instance.
<profiles xmlns="https://curity.se/ns/conf/base">
<profile>
<id>oauth-dev</id>
<type xmlns:as="https://curity.se/ns/conf/profile/oauth">as:oauth-service</type>
<settings>
<authorization-server xmlns="https://curity.se/ns/conf/profile/oauth">
<token-procedure-plugins>
<token-procedure-plugin>
<id>custom-token-procedure-plugin</id>
<custom-token-procedure-plugin xmlns="https://curity.se/ns/ext-conf/custom-token-procedure-plugin">
<my-issuer>
<id>CustomAccessTokenIssuer</id>
</my-issuer>
</custom-token-procedure-plugin>
</token-procedure-plugin>
</token-procedure-plugins>
</authorization-server>
</settings>
</profile>
</profiles>
Note how the element name my-issuer matches the getter name getMyIssuer() in the plugin’s configuration (declared in the step 1 above). A token procedure plugin can only use issuers defined in the same token profile.
As can be seen above, a custom token issuer can be accessed via the plugin’s configuration interface (using the plugin’s service injection mechanism). This is different from script token procedures where custom token issuers are accessed using the token procedure’s context object that is provided to the procedure’s function as the only parameter (e.g. context.getAccessTokenIssuer("CustomAccessTokenIssuer")). All of the issuer interfaces present in the SDK’s se.curity.identityserver.sdk.service.issuer package can be injected into token procedure plugins using the above mechanism.
There are two issuer types that are treated differently: DelegationIssuer and DeviceCodeIssuer. It’s not possible to create a custom version of these two issuers, however a default DelegationIssuer and a default DeviceCodeIssuer is always created for each token profile. Injecting these issuers into a token procedure plugin always results in the default issuer being provided. Since it’s not possible to configure a custom DelegationIssuer or DeviceCodeIssuer, using them in a token procedure plugin doesn’t require executing steps 3 and 4 from the above. It’s enough to just add a getter to the plugin’s configuration interface and then use the issuer in the token procedure’s run method.
Note that default issuers are always available via the plugin’s context object (similarly to how they’re available in script token procedures). They may also be obtained using the @DefaultService annotation mechanism.
Using Custom Token Introspecters#
Similarly to token issuers it’s also possible to inject and use token introspecters in token procedure plugins. The steps below highlight what needs to be done.
- Add a getter for the introspecter to the plugin’s configuration interface.
Example token procedure plugin configuration with a getter for an access token introspecter.
interface TokenProcedurePluginConfiguration : Configuration {
fun getMyIntrospecter(): AccessTokenIntrospecter
}
- Use the token introspecter in the token procedure plugin (via the configuration getter).
Example of using an access token introspecter in a token procedure plugin.
class CustomClientCredentialsTokenProcedure(
private val config: TokenProcedurePluginConfiguration,
) : ClientCredentialsTokenProcedure {
override fun run(context: ClientCredentialsTokenProcedurePluginContext): ResponseModel {
val presentedTokenValue = context.request.getFormParameterValueOrError("access-token")
val accessTokenIntrospecter = config.getMyIntrospecter()
var accessTokenAttributes = accessTokenIntrospecter.introspect(presentedTokenValue).orElseThrow()
// manipulate access token attributes (i.e. change some claims)
accessTokenAttributes = AccessTokenAttributes.of(
accessTokenAttributes.with(Attribute.of("extra_claim", "extra claim value"))
)
val delegation = context.delegationIssuer.issue(context.defaultDelegationData)
val accessToken = context.accessTokenIssuer.issue(accessTokenAttributes, delegation)
return ResponseModel.mapResponseModel(mapOf("new_access_token" to accessToken))
}
}
- Configure an access token introspecter in the server configuration under a token profile.
Example of a configuration for an access token introspecter.
<custom-token-issuer>
<id>CustomAccessTokenIntrospecter</id>
<issuer-type>opaque</issuer-type>
<purpose-type>access_token</purpose-type>
<data-sources>
<tokens-data-source-id>DefaultHSQLDB</tokens-data-source-id>
</data-sources>
</custom-token-issuer>
See Custom Token Issuers for more information about custom token issuers configuration. Note: In the server’s configuration, introspecters and issuers are the same thing. So in order to use an introspecter, a token issuer needs to be configured.
- Configure the token introspecter for a token procedure plugin instance.
Configuring a token introspecter in a plugin instance.
<profiles xmlns="https://curity.se/ns/conf/base">
<profile>
<id>oauth-dev</id>
<type xmlns:as="https://curity.se/ns/conf/profile/oauth">as:oauth-service</type>
<settings>
<authorization-server xmlns="https://curity.se/ns/conf/profile/oauth">
<token-procedure-plugins>
<token-procedure-plugin>
<id>custom-token-procedure-plugin</id>
<custom-token-procedure-plugin xmlns="https://curity.se/ns/ext-conf/custom-token-procedure-plugin">
<my-introspecter>
<id>CustomAccessTokenIntrospecter</id>
</my-introspecter>
</custom-token-procedure-plugin>
</token-procedure-plugin>
</token-procedure-plugins>
</authorization-server>
</settings>
</profile>
</profiles>
Note how the element name my-introspecter matches the getter name getMyIntrospecter() in the plugin’s configuration (declared in the step 1 above). A token procedure plugin can only use introspecters defined in the same token profile.
It’s important to understand that from the server’s configuration perspective, both custom token issuers and token introspecters are the same thing. In order to inject an introspecter, a token issuer has to be configured and then referenced from the token procedure plugin’s configuration, as can be seen in steps 3 and 4 above. The same configured issuer can be injected into a token procedure plugin as an issuer (e.g. using the AccessTokenIssuer type) and as an introspecter (e.g. using the AccessTokenIntrospecter type). A general rule is that an introspecter can introspect tokens issued by a custom issuer having the same id.