The Curity Security Token Server uses token procedures to issue tokens (see Issuing OAuth and OpenId Connect Tokens). These procedures can be defined as JavaScript scripts, described on Token procedures, or as SDK plugins, as documented in this section.
A token procedure plugin is an SDK plugin providing one or more procedures. Token procedures provided via plugins are specific to an endpoint (e.g. token endpoint) and to a flow on that endpoint (e.g. authorization code grant), as illustrated in the following table:
AuthorizeCodeTokenProcedure
OpenIdAuthorizeEndpointHybridTokenProcedure
AuthorizeImplicitTokenProcedure
DeviceAuthorizationTokenProcedure
IntrospectionTokenProcedure
IntrospectionApplicationJwtTokenProcedure
AssertionTokenProcedure
AuthorizationCodeTokenProcedure
BackchannelAuthenticationTokenProcedure
ClientCredentialsTokenProcedure
DeviceCodeTokenProcedure
RefreshTokenProcedure
RopcTokenProcedure
TokenExchangeTokenProcedure
PreAuthorizedCodeTokenProcedure
AssistedTokenProcedure
OpenIdUserInfoTokenProcedure
A token plugin can provide zero or one procedure for each one of the above types.
Token plugins are configured on a token service profile, more specifically on the token procedure plugins list.
The following two figures illustrate listing and adding a token procedure plugin.
Fig. 168 Listing token procedure plugins.
Fig. 169 Adding a token procedure plugin.
After a token procedure plugin is configured on a token service profile then it can be used by an endpoint of that same token service profile, by setting the plugin identifier (e.g. example-procedure-plugin-1) on the endpoint configuration (e.g. authorization endpoint configuration).
example-procedure-plugin-1
The following figure illustrates using the procedure provided by the example-procedure-plugin-1 on the authorization endpoint and for the authorization code flow.
Fig. 170 Using a procedure provided by a token procedure plugin on an endpoint.
Note that this association is only possible if the token procedure plugin contains internally a token procedure of the type required by the endpoint and flow being configured. Note also that it is not necessary to select which internal procedure to use from the plugin, since a token procedure plugin can contain at most one procedure of each type.
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 Plugins 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:
TokenProcedurePluginDescriptor
getOAuthAuthorizeEndpointCodeTokenProcedure
Class
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.
run
ResponseModel
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
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 plugins SDK Javadocs <https://curity.io/docs/idsvr-java-plugin-sdk/latest>, contains information about all the available procedure types, as well as the context classes that they may receive.
TokenProcedure
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.
Listing 156 Example token procedure plugin configuration with a getter for a custom access token issuer.¶ 1 2 3interface TokenProcedurePluginConfiguration : Configuration { fun getMyIssuer(): AccessTokenIssuer }
1 2 3
interface TokenProcedurePluginConfiguration : Configuration { fun getMyIssuer(): AccessTokenIssuer }
Use the custom token issuer in the token procedure plugin (via the configuration getter).
Listing 157 Example of using a custom access token issuer in a token procedure plugin.¶ 1 2 3 4 5 6 7 8 9 10 11class 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)) } }
1 2 3 4 5 6 7 8 9 10 11
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.
Listing 158 Example of a configuration for a custom access token issuer.¶ 1 2 3 4 5 6 7 8<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 Issuing OAuth and OpenId Connect Tokens for more information about custom token issuers configuration.
1 2 3 4 5 6 7 8
<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 Issuing OAuth and OpenId Connect Tokens for more information about custom token issuers configuration.
Configure the custom token issuer for a token procedure plugin instance.
Listing 159 Configuring a custom token issuer in a plugin instance.¶ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20<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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
<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.
my-issuer
getMyIssuer()
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.
context
context.getAccessTokenIssuer("CustomAccessTokenIssuer")
se.curity.identityserver.sdk.service.issuer
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.
DelegationIssuer
DeviceCodeIssuer
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.
@DefaultService
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.
Listing 160 Example token procedure plugin configuration with a getter for an access token introspecter.¶ 1 2 3interface TokenProcedurePluginConfiguration : Configuration { fun getMyIntrospecter(): AccessTokenIntrospecter }
interface TokenProcedurePluginConfiguration : Configuration { fun getMyIntrospecter(): AccessTokenIntrospecter }
Use the token introspecter in the token procedure plugin (via the configuration getter).
Listing 161 Example of using an access token introspecter in a token procedure plugin.¶ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19class 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)) } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
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.
Listing 162 Example of a configuration for an access token introspecter.¶ 1 2 3 4 5 6 7 8<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 Issuing OAuth and OpenId Connect Tokens 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.
<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 Issuing OAuth and OpenId Connect Tokens 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.
Listing 163 Configuring a token introspecter in a plugin instance.¶ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20<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.
<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.
my-introspecter
getMyIntrospecter()
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.
AccessTokenIssuer
AccessTokenIntrospecter
id
Footnotes