Stateless Ticket Registry

The stateless ticket registry is a ticket registry that does not track or store tickets in a persistent manner via a backend storage technology. All generated tickets are self-contained and are able to carry their own state which in turn makes them portable across CAS nodes and clustered deployments. Each ticket is digitally encrypted to ensure its integrity and confidentiality. Furthermore, generated tickets are compressed as much as possible and are constrained to a pre-defined size to ensure backward compatibility with various CAS clients where possible.

Support is enabled by including the following dependency in the WAR overlay:

1
2
3
4
5
<dependency>
    <groupId>org.apereo.cas</groupId>
    <artifactId>cas-server-support-stateless-ticket-registry</artifactId>
    <version>${cas.version}</version>
</dependency>
1
implementation "org.apereo.cas:cas-server-support-stateless-ticket-registry:${project.'cas.version'}"
1
2
3
4
5
6
7
8
9
dependencyManagement {
    imports {
        mavenBom "org.apereo.cas:cas-server-support-bom:${project.'cas.version'}"
    }
}

dependencies {
    implementation "org.apereo.cas:cas-server-support-stateless-ticket-registry"
}
1
2
3
4
5
6
7
8
9
10
dependencies {
    /*
        The following platform references are included automatically and are listed for reference only.

        implementation enforcedPlatform("org.apereo.cas:cas-server-support-bom:${project.'cas.version'}")
        implementation platform(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES)
        
    */
    implementation "org.apereo.cas:cas-server-support-stateless-ticket-registry"
}

The following settings and properties are available from the CAS configuration catalog:

cas.ticket.registry.stateless.crypto.encryption.keyThe encryption key.
no default
Required

The encryption key. The encryption key by default and unless specified otherwise must be randomly-generated string whose length is defined by the encryption key size setting.

Type
String
Default
none
Defined by
EncryptionRandomizedCryptoProperties
cas.ticket.registry.stateless.crypto.signing.keyThe signing key is a string whose length is defined by the signing key size setting.
no default
RequiredSpEL

The signing key is a string whose length is defined by the signing key size setting.

Type
String
Default
none
Defined by
SigningJwtCryptoProperties
Supports
Spring Expression Language
cas.ticket.registry.stateless.crypto.algThe signing/encryption algorithm to use.
AES

The signing/encryption algorithm to use.

Type
String
Default
AES
Defined by
EncryptionRandomizedSigningJwtCryptographyProperties
cas.ticket.registry.stateless.crypto.enabledWhether crypto operations are enabled.
true

Whether crypto operations are enabled.

Type
Boolean
Default
true
Defined by
EncryptionRandomizedSigningJwtCryptographyProperties
cas.ticket.registry.stateless.crypto.encryption.key-sizeEncryption key size.
16

Encryption key size.

Type
Integer
Default
16
Defined by
EncryptionRandomizedCryptoProperties
cas.ticket.registry.stateless.crypto.signing-enabledWhether signing encryption operations are enabled.
true

Whether signing encryption operations are enabled.

Type
Boolean
Default
true
Defined by
EncryptionRandomizedSigningJwtCryptographyProperties
cas.ticket.registry.stateless.crypto.signing.key-sizeThe signing key size.
512

The signing key size.

Type
Integer
Default
512
Defined by
SigningJwtCryptoProperties

Required settings may be needed to activate or affect the feature; review them even when they have a default. Optional settings only need to be set to change a default or to turn on the behavior they control. Third party settings belong to libraries such as Spring Boot that CAS builds on; their own documentation may have more detail.

Signing & encryption

This CAS feature is able to accept signing and encryption crypto keys. In most scenarios if keys are not provided, CAS will auto-generate them. The following instructions apply if you wish to manually and beforehand create the signing and encryption keys.

Note that if you are asked to create a JWK of a certain size for the key, you are to use the following set of commands to generate the token:

1
2
wget https://raw.githubusercontent.com/apereo/cas/master/etc/jwk-gen.jar
java -jar jwk-gen.jar -t oct -s [size]

The outcome would be similar to:

1
2
3
4
5
{
  "kty": "oct",
  "kid": "...",
  "k": "..."
}

The generated value for k needs to be assigned to the relevant CAS settings. Note that keys generated via the above algorithm are processed by CAS using the Advanced Encryption Standard (AES) algorithm which is a specification for the encryption of electronic data established by the U.S. National Institute of Standards and Technology.


Notes on configuration

Configuration Metadata

The collection of configuration properties listed in this section are automatically generated from the CAS source and components that contain the actual field definitions, types, descriptions, modules, etc. This metadata may not always be 100% accurate, or could be lacking details and sufficient explanations.

Be Selective

This section is meant as a guide only. Do NOT copy/paste the entire collection of settings into your CAS configuration; rather pick only the properties that you need. Do NOT enable settings unless you are certain of their purpose and do NOT copy settings into your configuration only to keep them as reference. All these ideas lead to upgrade headaches, maintenance nightmares and premature aging.

YAGNI

Note that for nearly ALL use cases, declaring and configuring properties listed here is sufficient. You should NOT have to explicitly massage a CAS XML/Java/etc configuration file to design an authentication handler, create attribute release policies, etc. CAS at runtime will auto-configure all required changes for you. If you are unsure about the meaning of a given CAS setting, do NOT turn it on without hesitation. Review the codebase or better yet, ask questions to clarify the intended behavior.

Naming Convention

Property names can be specified in very relaxed terms. For instance cas.someProperty, cas.some-property, cas.some_property are all valid names. While all forms are accepted by CAS, there are certain components (in CAS and other frameworks used) whose activation at runtime is conditional on a property value, where this property is required to have been specified in CAS configuration using kebab case. This is both true for properties that are owned by CAS as well as those that might be presented to the system via an external library or framework such as Spring Boot, etc.

:information_source: Note

When possible, properties should be stored in lower-case kebab format, such as cas.property-name=value. The only possible exception to this rule is when naming actuator endpoints; The name of the actuator endpoints (i.e. ssoSessions) MUST remain in camelCase mode.

Settings and properties that are controlled by the CAS platform directly always begin with the prefix cas. All other settings are controlled and provided to CAS via other underlying frameworks and may have their own schemas and syntax. BE CAREFUL with the distinction. Unrecognized properties are rejected by CAS and/or frameworks upon which CAS depends. This means if you somehow misspell a property definition or fail to adhere to the dot-notation syntax and such, your setting is entirely refused by CAS and likely the feature it controls will never be activated in the way you intend.

Validation

Configuration properties are automatically validated on CAS startup to report issues with configuration binding, especially if defined CAS settings cannot be recognized or validated by the configuration schema. Additional validation processes are also handled via Configuration Metadata and property migrations applied automatically on startup by Spring Boot and family.

Indexed Settings

CAS settings able to accept multiple values are typically documented with an index, such as cas.some.setting[0]=value. The index [0] is meant to be incremented by the adopter to allow for distinct multiple configuration blocks.

Features

  • No centralized backend storage or caching technology is required to be present, configured, installed, managed, maintained, tuned, etc.
  • …as a result, you do not need to worry about storage schema upgrades, migrations, etc.
  • …as a result, you do not need to worry about cleaning up expired tickets or garbage-collecting ticket entities.
  • …as a result, you do not need to worry about sharing tickets across CAS nodes in a clustered deployment and synchronizing state.
  • …as a result, you do not need to pay for storage or possible caching technology licenses especially if your CAS deployment is cloud-native.

The above features do come with a number of caveats and limitations. See below.

Supported Protocols

:information_source: What About...?

Remember that not all CAS modules and features that interact with the ticket registry to create, update, fetch or remove tickets are supported. The objective is to start with a small batch of most common features and capabilities and iteratively grow and improve. If you do find something that might be missing or acts dysfunctional, please investigate, isolate, verify and consider contributing a fix.

Suggestions

  • Increase the expiration policy of service tickets to be around 30 seconds to allow for decryption operations to decode tickets in time.
  • Assign names to all authentication handlers, and preferably short, concise names.
  • Use shorter URLs for applications, especially those that use the CAS protocol. This will help minimize the size of the generated service tickets.
  • Turn off ticket-granting cookie signing and keep its encryption, to keep the cookie within browser limits.

The ticket-granting cookie carries the entire stateless ticket-granting ticket, including the authenticated principal id, the credentials, and the authentication attributes. Principal attributes are not kept in the ticket, as noted below. The size of the cookie grows with the number and size of the authentication attributes and credentials, and multifactor authentication providers such as Duo Security can add many of their own. Browsers only guarantee cookies of up to 4096 bytes and silently drop larger ones, in which case every request asks the user to sign in again. CAS logs a warning when the cookie exceeds this size:

1
WARN <Cookie [TGC] is [4436] bytes, larger than the [4096] bytes browsers are guaranteed to accept...>

You may turn off cookie signing and keep cookie encryption, which makes the cookie about a quarter smaller:

The following settings and properties are available from the CAS configuration catalog:

cas.tgc.crypto.encryption.keyThe encryption key is a string whose length is defined by the encryption key size setting.
no default
RequiredSpEL

The encryption key is a string whose length is defined by the encryption key size setting.

Type
String
Default
none
Defined by
EncryptionJwtCryptoProperties
Supports
Spring Expression Language
cas.tgc.crypto.signing.keyThe signing key is a string whose length is defined by the signing key size setting.
no default
RequiredSpEL

The signing key is a string whose length is defined by the signing key size setting.

Type
String
Default
none
Defined by
SigningJwtCryptoProperties
Supports
Spring Expression Language
cas.tgc.crypto.algThe signing/encryption algorithm to use.
no default

The signing/encryption algorithm to use.

Type
String
Default
none
Defined by
EncryptionOptionalSigningOptionalJwtCryptographyProperties
cas.tgc.crypto.enabledWhether crypto operations are enabled.
true

Whether crypto operations are enabled.

Type
Boolean
Default
true
Defined by
EncryptionOptionalSigningOptionalJwtCryptographyProperties
cas.tgc.crypto.encryption-enabledWhether crypto encryption operations are enabled.
true

Whether crypto encryption operations are enabled.

Type
Boolean
Default
true
Defined by
EncryptionOptionalSigningOptionalJwtCryptographyProperties
cas.tgc.crypto.encryption.key-sizeThe encryption key size.
512

The encryption key size.

Type
Integer
Default
512
Defined by
EncryptionJwtCryptoProperties
cas.tgc.crypto.signing-enabledWhether crypto signing operations are enabled.
true

Whether crypto signing operations are enabled.

Type
Boolean
Default
true
Defined by
EncryptionOptionalSigningOptionalJwtCryptographyProperties
cas.tgc.crypto.signing.key-sizeThe signing key size.
512

The signing key size.

Type
Integer
Default
512
Defined by
SigningJwtCryptoProperties
cas.tgc.crypto.strategy-typeControl the cipher sequence of operations.
ENCRYPT_AND_SIGN
Control the cipher sequence of operations. The accepted values are:
  • ENCRYPT_AND_SIGN: Encrypt the value first, and then sign.
  • SIGN_AND_ENCRYPT: Sign the value first, and then encrypt.
Type
String
Default
ENCRYPT_AND_SIGN
Defined by
EncryptionOptionalSigningOptionalJwtCryptographyProperties

Required settings may be needed to activate or affect the feature; review them even when they have a default. Optional settings only need to be set to change a default or to turn on the behavior they control. Third party settings belong to libraries such as Spring Boot that CAS builds on; their own documentation may have more detail.

Signing & encryption

This CAS feature is able to accept signing and encryption crypto keys. In most scenarios if keys are not provided, CAS will auto-generate them. The following instructions apply if you wish to manually and beforehand create the signing and encryption keys.

Note that if you are asked to create a JWK of a certain size for the key, you are to use the following set of commands to generate the token:

1
2
wget https://raw.githubusercontent.com/apereo/cas/master/etc/jwk-gen.jar
java -jar jwk-gen.jar -t oct -s [size]

The outcome would be similar to:

1
2
3
4
5
{
  "kty": "oct",
  "kid": "...",
  "k": "..."
}

The generated value for k needs to be assigned to the relevant CAS settings. Note that keys generated via the above algorithm are processed by CAS using the Advanced Encryption Standard (AES) algorithm which is a specification for the encryption of electronic data established by the U.S. National Institute of Standards and Technology.


Notes on configuration

Configuration Metadata

The collection of configuration properties listed in this section are automatically generated from the CAS source and components that contain the actual field definitions, types, descriptions, modules, etc. This metadata may not always be 100% accurate, or could be lacking details and sufficient explanations.

Be Selective

This section is meant as a guide only. Do NOT copy/paste the entire collection of settings into your CAS configuration; rather pick only the properties that you need. Do NOT enable settings unless you are certain of their purpose and do NOT copy settings into your configuration only to keep them as reference. All these ideas lead to upgrade headaches, maintenance nightmares and premature aging.

YAGNI

Note that for nearly ALL use cases, declaring and configuring properties listed here is sufficient. You should NOT have to explicitly massage a CAS XML/Java/etc configuration file to design an authentication handler, create attribute release policies, etc. CAS at runtime will auto-configure all required changes for you. If you are unsure about the meaning of a given CAS setting, do NOT turn it on without hesitation. Review the codebase or better yet, ask questions to clarify the intended behavior.

Naming Convention

Property names can be specified in very relaxed terms. For instance cas.someProperty, cas.some-property, cas.some_property are all valid names. While all forms are accepted by CAS, there are certain components (in CAS and other frameworks used) whose activation at runtime is conditional on a property value, where this property is required to have been specified in CAS configuration using kebab case. This is both true for properties that are owned by CAS as well as those that might be presented to the system via an external library or framework such as Spring Boot, etc.

:information_source: Note

When possible, properties should be stored in lower-case kebab format, such as cas.property-name=value. The only possible exception to this rule is when naming actuator endpoints; The name of the actuator endpoints (i.e. ssoSessions) MUST remain in camelCase mode.

Settings and properties that are controlled by the CAS platform directly always begin with the prefix cas. All other settings are controlled and provided to CAS via other underlying frameworks and may have their own schemas and syntax. BE CAREFUL with the distinction. Unrecognized properties are rejected by CAS and/or frameworks upon which CAS depends. This means if you somehow misspell a property definition or fail to adhere to the dot-notation syntax and such, your setting is entirely refused by CAS and likely the feature it controls will never be activated in the way you intend.

Validation

Configuration properties are automatically validated on CAS startup to report issues with configuration binding, especially if defined CAS settings cannot be recognized or validated by the configuration schema. Additional validation processes are also handled via Configuration Metadata and property migrations applied automatically on startup by Spring Boot and family.

Indexed Settings

CAS settings able to accept multiple values are typically documented with an index, such as cas.some.setting[0]=value. The index [0] is meant to be incremented by the adopter to allow for distinct multiple configuration blocks.

:warning: Signing Key

Signing remains active as long as the signing key is defined. Remove the signing key to turn signing off, and do not turn off cookie encryption.

Caveats

The stateless ticket registry may not be a suitable solution for all deployment scenarios and its use and adoption does require a number of compromises and security trade-offs. The following is a list of limitations and caveats that one should be aware of:

:information_source: Life Advice

Depending on your point of view, any one of the caveats noted here could be argued as a minor lapse in security. Lessened security constraints around generated tickets or the inability to manage one's single sign-on session remotely, etc might be a deal breaker for you. Needless to say, you should examine and understand the security trade-offs carefully before you decide to use this option, or any option for that matter.

  • Tickets are not single-use. A service ticket, proxy ticket, OAuth authorization code, or a pre-authorized code or credential nonce of verifiable credentials can be validated, exchanged or used again and again until it expires, unlike what the CAS protocol and OAuth2 specifications require, so keep their expiration short. Expiration policies ignore usage counts and only enforce an expiration instant; the ticket-granting ticket expires at the end of its maximum lifetime, and any idle timeout configured for it is not enforced.
  • Issued tickets cannot be revoked. Logging out removes the ticket-granting cookie from the browser, but a copy of that cookie remains valid until the ticket-granting ticket expires. Likewise, revoking an OAuth access or refresh token has no effect before it expires.
  • Generated tickets are generally controlled to be no larger than 256 characters. You might need to adjust your servlet container of choice to allow for larger form/response header sizes. Likewise, you must ensure your applications, particularly those that deal with CAS or OpenID Connect protocols are OK with somewhat larger and longer ticket and token sizes.
  • Super long application URLs that might negatively influence the size of the generated service ticket are compressed using a pre-defined modest shortening technique, which in turn is taken into account by a specialized ticket validation strategy. For best results, and this is true for all CAS-supported protocols, it is recommended that applications use shorter URLs.
  • To minimize the length of the generated tickets, tickets are only encrypted.
  • The single sign-on session is tracked by the ticket-granting cookie as usual, which carries the stateless ticket-granting ticket itself. Browsers only guarantee cookies of up to 4096 bytes and silently drop larger ones, which ends the single sign-on session.
  • Important: Principal attributes produced and collected during the first leg of the authentication transaction are not kept in any ticket. The ticket-granting ticket keeps the principal id, and CAS fetches all principal attributes from configured attribute repositories once more every time the ticket-granting ticket is read, such as when single sign-on sessions are established for applications, and again during back-channel ticket validation attempts. As a result, single sign-on decisions such as multifactor authentication triggers, access strategies and single sign-on participation policies that are based on principal attributes, as well as attribute release, only see attributes that the attribute repositories produce. In other words, if your attributes are only produced once during the authentication transaction by an authentication handler and family, such as claims from delegated authentication or multifactor authentication providers, you must also configure an attribute repository to fetch the attributes yet again. The ticket-granting ticket keeps its authentication attributes as they are; service tickets, proxy tickets and OAuth or OpenID Connect tokens only carry the authentication method, the successful authentication handlers, the credential types, remember-me, the delegated identity provider name (clientName) and the multifactor authentication context and trusted device attributes, kept as text. Other authentication attributes are not released during ticket validation. Principals that do not accept new attributes, such as those produced by surrogate authentication, keep the attributes they were created with.
  • Every read of the ticket-granting ticket asks the configured attribute repositories for principal attributes. Results are cached for a period of time so most reads do not reach the repositories. If principal resolution fails with an error, the ticket-granting ticket is treated as missing and the user is asked to sign in again. Attribute repositories that do not respond may instead produce no attributes, depending on their configuration.
  • Sessions kept in the ticket registry, such as replicated sessions for delegated authentication or OAuth and OpenID Connect, or HTTP sessions stored in the ticket registry, travel in their session cookie as stateless tickets. They expire at a fixed instant after they are created, and since the whole session is in the cookie, it must stay well under 4096 bytes.
  • Replicated session cookies for delegated authentication and for OAuth and OpenID Connect are pinned to the browser by default: the client IP address and user agent are added to the cookie value. Since encryption for these cookies is turned off by default, that value is sent as plain text, and the spaces and separators in the user agent make browsers truncate or drop the cookie, so the session is lost on the next request. Turn on encryption for these cookies, or turn off pinning with the pin-to-session setting of the same cookies.
  • Password reset links carry the stateless ticket that tracks the request, so they are a few hundred to a few thousand characters long. A link is not single-use: it can be used again until it expires, regardless of the number of uses allowed for password reset links, so keep its expiration short.
  • Dynamic client registration returns a registration access token that is a stateless access token. Like other access tokens, it cannot be revoked and stays valid until it expires.
  • Pushed authorization requests are kept in the request_uri returned to the client, which is therefore a few hundred to a few thousand characters long and travels in the authorization URL. Like other tickets, a request_uri is not single-use and can be used again until it expires.
  • Delegated authentication to SAML2 identity providers sends the id of the ticket that tracks the authentication request as the SAML2 RelayState. With this registry, that id is the ticket itself, which is far longer than the 80 bytes the SAML2 bindings specification allows for RelayState. Identity providers that enforce this limit reject the request or return a different RelayState, and the response can no longer be matched to its request. Verify that your identity providers accept longer RelayState values before using delegated SAML2 authentication with this registry.
  • Simple multifactor authentication tokens are stored as stateless tickets while the user still receives and types the short code. The webflow keeps the stored token and checks the code against it, so a code is only accepted in the login flow that sent it. Tokens are not removed after use and stay valid until they expire. Tokens obtained from the REST endpoint are returned as stateless tickets, so the full ticket id, and not a short code, must be presented to validate them.
  • Duo Security session storage in the ticket registry (TICKET_REGISTRY) is not supported. The ticket that keeps the state of the authentication flow is sent to Duo Security as the state of the request, and with this registry that ticket carries the whole flow state, which does not fit in the 1024 characters Duo Security accepts. Set the Duo Security session storage type to BROWSER_STORAGE, which is the default.
  • In the absence of a central backend storage service, back-channel single logout operations are not supported. Likewise, all operations that ask for active single sign-on sessions or anything that in general deals with tracking single sign-on sessions is out of scope and unlikely to be supported. You will lose the ability to determine whether a user is logged in and as a result will be unable to administratively terminate a user’s session.