Release notes · All release candidates
8.1.0
Every change from RC1, RC2, RC3, RC4 in one place. Each change links to the release candidate it shipped in.
- 4Release candidates
- 37New & noteworthy topics
- 20Changes to review before upgrading
- 145Other changes
Before You Upgrade
These changes alter existing behavior. Each one links to its full description in the release candidate that introduced it.
-
Action
RC3 Certificate-bound access tokens issued before the upgrade are rejected; thumbprints now follow RFC 8705.OAuth & OpenID Connect
-
Action
RC3 Heimdall AuthZEN resources must define
resourceType,actionsand optionallyresourceIdPattern, or AuthZEN requests are denied.Authorization -
Changed
RC3 Heimdall policies deny access when a resource has none, and one granting policy is enough unless
enforceAllPoliciesis set.Authorization -
Removed
RC3
cas.ticket.registry.stateless.storage-typeno longer applies; the ticket-granting ticket is carried by the ticket-granting cookie.Tickets & Sessions -
Changed
RC3 Stateless ticket-granting tickets no longer carry principal attributes; attribute repositories are consulted each time instead.Tickets & Sessions
-
Changed
RC3 Authentication throttling counts a failed attempt only on a
401response, once per request.Authentication - Changed
-
Changed
RC3 Passwordless tokens arrive as
PasswordlessTokenCredential, the only credential the handler accepts; the recorded credential type changes.Passwordless -
Action
RC3 Google Authenticator on DynamoDB needs a new
useridIndexglobal secondary index; create it yourself if tables are not created at startup.Multifactor & Passkeys -
Action
RC2 Verifiable credential wallets must speak the final OpenID4VCI 1.0 wire format; draft 13 requests are no longer understood.OAuth & OpenID Connect
- Changed
-
Action
RC2 Groovy consent activation scripts must return a
ConsentQueryResultinstead of a boolean.Attributes & Consent -
Changed
RC2 CAS validation responses report
INVALID_TICKET_SPECandINTERNAL_ERRORwhere they used other codes, and replace characters that are not legal in XML attribute names with_.CAS & Other Protocols -
Changed
RC2 SAML2 metadata fails to load when its signature certificate cannot be read, and HTTP downloads are capped at 256 MiB by default.SAML2
-
Changed
RC2 Theme names without a matching theme definition are ignored, and the next resolver or the default theme applies.Interface & Accounts
-
Action
RC1 Palantir admin functions now require a released
roleattribute ofADMINorROLE_ADMIN; other users only manage registered applications.Platform & Operations - Changed
- Changed
-
Changed
RC1 CAS REST APIs answer unauthorized application requests with
403instead of a500error.CAS & Other Protocols - Removed
Highlights
-
AuthorizationChanged
Heimdall AuthZEN
Follows the AuthZEN Authorization API 1.0, with resource instances, the access evaluations API, decision point metadata and decision context.
RC3
-
Multifactor & PasskeysNew
WebAuthn Level 3
Related origins, the Signal API, passkey provider names and icons, and a passkey upgrade after password login.
RC3
-
PasswordlessNew
Passwordless Authentication
Single-use tokens under load, one-time-code autofill, origin-bound SMS codes and passkeys from the autofill menu.
RC3
-
Tickets & SessionsAction
Stateless Ticket Registry
The ticket-granting ticket now rides in the ticket-granting cookie, with a versioned format and type-bound tickets.
RC3
-
OAuth & OpenID ConnectNew
OpenID Connect Verifiable Credentials
Proofs follow each credential configuration, and holder keys may be
x5c,did:jwkor Ed25519.RC3RC2RC1
-
DocumentationNew
Documentation
A refreshed theme, searchable settings, grouped actuator endpoints and feature toggles, and a Quick Start recipe.
RC3
-
OAuth & OpenID ConnectChanged
OAuth & OpenID Connect Hardening
Replay protection across nodes, client-bound token exchange, introspection and DPoP, and spendable DPoP-bound tokens.
RC2
-
SAML2Changed
SAML2 Identity Provider
Independently validated request signatures, atomic metadata resolution, MDQ fixes and pooled HTTP clients.
RC2
-
CAS & Other ProtocolsFixed
CAS Protocol
Responses can no longer be forged through line breaks or markup, and
renewis evaluated per request.RC2
-
Attributes & ConsentFixed
Attribute Consent
Every consent decision gets its own identifier, and lookups and revocations work across all stores.
RC2
-
Platform & OperationsNew
JMX Management
Service reloads, ticket and session counts, cleanup and diagnostics are now available over JMX.
RC2
-
OAuth & OpenID ConnectNew
Multiple Client Secrets
Applications can hold several expiring client secrets for smooth rotation, managed through the
oauthClientSecretsendpoint.RC1
-
Platform & OperationsNew
Cluster Topology
A
clusterTopologyactuator endpoint reports every node and its status across eight ticket registries.RC1
-
Attributes & ConsentNew
Attribute Definition Dependencies
Attribute definitions can depend on other definitions, which are resolved first and available during resolution.
RC1
-
OAuth & OpenID ConnectChanged
OpenID Connect Federation
Federation support is roughly finalized, with remaining edge cases tracked for the next releases.
RC1
-
Interface & AccountsNew
Acceptable Usage Policy & Multitenancy
Acceptable usage policies work per tenant, starting with MongoDb storage.
RC1
New & Noteworthy
Topics from every release candidate, grouped by area. When a topic appears in more than one release candidate, the notes of each are shown, newest first.
Authentication
Authentication Throttling
Authentication throttling now records a failed attempt only
when the response status is 401, once per request. Previously, any response other than 200, 201 or 302, such as a malformed
request (400), an authorization denial (403), a missing resource (404) or a server error (500), was counted as a failed login,
and failures were recorded twice. Failed SAML2 ECP authentication attempts, which answer with a SOAP fault, are now counted as well.
Redis authentication throttling no longer reads the audit log.
It used to scan and load every audit record stored in Redis on each login attempt, which slowed every login as the audit log grew.
Failed attempts are now tracked in a small key per username and IP address that expires once outside the failure range.
Redis audit records now expire after cas.audit.redis.max-age, 180 days by default, and
looking up audit records reads only the newest matching entries instead of loading every record.
Audit events for the same principal within one second are now stored separately; previously, later events could overwrite
earlier ones, including authentication successes immediately followed by ticket creation. Existing records remain readable.
LDAP Integrations
- Surrogate authentication, delegated authentication profile selection, acceptable usage policy and password management now build their LDAP connection pools once and reuse them across requests.
- LDAP connection pools are now addressed by the directory and base DN they serve, so multiple configuration blocks that point at the same server no longer collapse onto a single set of connection settings.
- Surrogate search filters that do not reference the impersonated account are now rejected, as such a filter is unable to restrict the accounts a user may impersonate.
Passwordless
Passwordless Authentication
- Passwordless authentication tokens are now single-use under concurrent submissions, and every submitted token, including a wrong one, goes through the authentication manager and is recorded in the audit log.
- Tokens kept in JPA or MongoDb are now removed once used, and their cleaner removes expired tokens; it used to remove the valid ones.
- MongoDb and REST stores no longer return expired
tokens, and a REST endpoint must answer a single-token
DELETEwith a2xxstatus only when it removed the token. - A token that could not be delivered by email or SMS is no longer stored and the user is told so, while a failure in one channel no longer discards a token the other one delivered.
- Submitted tokens now arrive as a dedicated
PasswordlessTokenCredential, which is the only credential the passwordless authentication handler accepts; other one-time password credentials, such as Duo Security passcodes, are no longer checked against the passwordless token store, and the recorded credential type changes accordingly. - The token field is now a plain text field marked as
one-time-code, so browsers and phones can fill in the code. A wrong token no longer causes a new token to be issued and sent. - SMS messages now end with an
origin-bound one-time code line (
@host #token), which the token page reads through the WebOTP API where available. - When WebAuthn primary authentication is allowed, the passwordless username field offers discoverable passkeys from the browser’s autofill menu (WebAuthn conditional mediation) where the browser supports it, and the passwordless selection menu offers a passkey option. Both hand the passkey assertion to the existing WebAuthn primary authentication flow.
A dedicated actuator endpoint, passwordless, is available to allows one to query a username
and retrieve the associated passwordless account information.
This capability is also supported and available for the Palantir Admin Dashboard.
Multifactor & Passkeys
WebAuthn Level 3
- FIDO2 WebAuthn publishes its origins at
/.well-known/webauthnfor related origin requests, so passkeys can be used from origins whose domain differs from the relying party identifier. - After a successful authentication, CAS reports the user’s accepted passkeys and current account details to the browser
through the Signal API, and passkey autofill checks
getClientCapabilities()where the browser offers it. - Registration and authentication requests carry the user-agent hints set in
cas.authn.mfa.web-authn.core.hints, and each registration records whether the authenticator reported a discoverable credential (credProps). - When an assertion fails because the owning account no longer holds the passkey, the response says so and the browser
is told to stop offering it (
signalUnknownCredential); the account profile does the same as soon as a passkey is deleted, which also covers the account’s last passkey. - WebAuthn pages now use the browser’s JSON serialization (
parseCreationOptionsFromJSON,parseRequestOptionsFromJSON,toJSON()) and no longer override the configured attestation conveyance preference withdirect; browsers without this WebAuthn Level 3 support can no longer use WebAuthn in CAS. - CAS also publishes
/.well-known/passkey-endpointsso password managers can link users to the pages where passkeys are created and managed; by default both point to the account profile when account management is enabled. - When WebAuthn primary authentication is allowed, the default registration button now asks for a discoverable
credential (
residentKey=preferred), so passkeys registered with it can also log in on their own. - Each registration now keeps the authenticator’s backup eligibility and backup state, which are updated on every login; a passkey whose backup eligibility changes is rejected, as WebAuthn Level 3 requires. The transports recorded at registration are sent back with the credentials CAS lists to the browser, so it can reach each authenticator directly.
- Registrations also keep the authenticator’s AAGUID, and the account profile names passkeys by provider (for example Google Password Manager or 1Password) with the provider’s icon when the attestation does not name the device; the registration and login pages show that name and icon for the passkey just registered or used.
- A new passkey upgrade, turned on with
cas.authn.mfa.web-authn.core.passkey-upgrade-enabledalongside primary authentication and untrusted attestation, shows a short page after a password login that lets the browser’s password manager create a passkey for the account on its own (WebAuthn conditional create), then continues as usual. Such passkeys keep the typed username as their name when CAS later reports account details through the Signal API.
WebAuthn Multifactor Authentication
FIDO2 WebAuthn multifactor authentication receives the following improvements:
- Authentication and QR session proofs are now principal-bound and single-use, and configured user verification is enforced for assertions.
- Credential and user-handle resolution now avoids repeated repository-wide scans.
Duo Multifactor Authentication
Duo Security multifactor authentication receives the following improvements:
- REST passcodes now require completed primary authentication, and Universal Prompt callbacks validate browser-session state before restoring the flow.
- Duo Auth and Admin API clients now reuse outbound connection pools across requests.
OAuth & OpenID Connect
Certificate-Bound Access Tokens
The certificate thumbprint that CAS records for mutual TLS client authentication
and emits as the cnf x5t#S256 claim of access tokens and introspection responses is now computed as specified
by RFC 8705: the base64url-encoded SHA-256 hash of the DER-encoded certificate.
Previously, it was computed from the certificate’s public key and was not encoded as specified. Certificate-bound tokens issued
before the upgrade carry the old value and are no longer accepted by resource servers that verify the binding.
OpenID Connect Verifiable Credentials
- Credential proofs now follow what each credential configuration
advertises: a proof signed with an algorithm outside
proof-signing-alg-values-supported, or naming its key by a binding method outsidecryptographic-binding-methods-supported, is refused withinvalid_proof. Holder keys may now also be given as anx5ccertificate or adid:jwkkey identifier, and Ed25519 (EdDSA) keys are accepted. - Authorization server metadata advertises
pre-authorized_grant_anonymous_access_supportedwhenever the pre-authorized code grant is supported, so wallets without a client registration know they may redeem a pre-authorized code. - A wallet may request a credential in the authorization code flow by the
scopeits credential configuration publishes, as OpenID4VCI 1.0 allows, instead of authorization details. Such scopes were previously dropped unless listed among the discovery scopes, and the resulting token was refused at the credential endpoint. - A wallet that declines a verifiable presentation
request can now say so: its error response is accepted, answered as OpenID4VP requires, and reported to the relying party
as an
erroroutcome once the request expires. Since nothing authenticates an error response, it does not end the request, and a valid presentation that arrives before the request expires takes precedence. Previously error responses were rejected. - Issuer metadata describes each credential format as OpenID4VCI 1.0 requires:
jwt_vc_jsonandjwt_vc_json-ldconfigurations publishcredential_definitioninstead ofvct. JSON-LD credentials no longer reference a context document CAS never served, and a configuration without a scope no longer issues anullcredential type. - CAS publishes JWT VC Issuer Metadata at
/.well-known/jwt-vc-issuer, so verifiers other than CAS can find the keys that sign the credentials it issues. Deployments under a context path should addjwt-vc-issuerto the well-known rewrite rule. - The verifier accepts RSA and Ed25519 holder keys in addition to EC keys, advertises those key binding algorithms,
and advertises the signing algorithms of its
dc+sd-jwtcredential configurations instead of a fixed list. - A presentation request may carry a registered
redirect_urifor a same-device flow: the wallet is sent back to it with aresponse_code, which the relying party must present to collect the outcome. Outcomes are now released only to the client that created the request. - Claims marked
"required": falsein a presentation request are now optional: they are requested through DCQLclaim_setsand may be withheld. Previously the flag was ignored and every claim was required. - Issuer metadata can describe the issuer for wallets via
cas.authn.oidc.vc.issuer.display(name, language, logo). - Credential offer transactions also return the
openid-credential-offer://deep link for the offer. - Issued credentials no longer carry
client_id(claim or header) orcredential_configuration_id, which revealed to every verifier which relying party requested the credential; CAS verifies its own credentials by the key’skid. - A wallet’s error response is stored bounded:
errorto 128 characters anderror_descriptionto 1024. - Attribute values with a leading zero, such as postal codes, are issued as text. Previously they were read as octal numbers,
so
0123was issued as83and08failed issuance. - Issued credentials carry the issuer signing key’s certificate chain as the
x5cheader, without the trust anchor, as HAIP 1.0 requires, when the key in the keystore has one. - Signed presentation request objects (
X509_SAN_DNS) no longer include the trust anchor in theirx5cheader, as HAIP 1.0 requires. - A self-signed credential or request object signing certificate, which HAIP 1.0 forbids, is still used but now logs a warning.
- Refresh tokens keep the authorization details of the authorization code they were issued for, so access tokens obtained by refreshing may still request the credentials those details granted. Previously only scope-based grants survived a refresh.
- Verifiable presentation responses can be
encrypted (
direct_post.jwt), as the High Assurance Interoperability Profile requires, withcas.authn.oidc.vc.presentation.response-mode=DIRECT_POST_JWTor per request with"response_mode": "direct_post.jwt": each request carries its own ephemeralECDH-ESkey, and presentations sent in the clear are refused. - Verifiable presentation requests may identify CAS with the
x509_hashclient identifier prefix, which the High Assurance Interoperability Profile requires of verifiers that sign requests:cas.authn.oidc.vc.presentation.client-identifier-prefix=X509_HASH. - Verifiable presentations may be requested through the W3C Digital Credentials API
with the
dc_apianddc_api.jwtresponse modes: CAS builds the request the relying party page passes to the browser, and verifies what the page posts back, bound to the page’s origin. - Credential requests may carry key attestations, as
OpenID4VCI 1.0 and the High Assurance Interoperability Profile define: in the
key_attestationheader of ajwtproof, or as anattestationproof that yields one credential per attested key. Attestations must chain to trust anchors set withcas.authn.oidc.vc.issuer.key-attestation.trust-anchors, and a credential configuration may require them, along with the key storage and user authentication levels it accepts, which the issuer metadata advertises askey_attestations_required. - Clients, wallets above all, may authenticate at the token and pushed authorization request endpoints with a client attestation
and its proof of possession, per OAuth 2.0 Attestation-Based Client Authentication
(
attest_jwt_client_auth), as the High Assurance Interoperability Profile requires of wallet attestations. Attestations must chain to trust anchors set withcas.authn.oidc.client-attestation.trust-anchors, which also advertises the method in the discovery document. - Wallets may report what became of issued credentials at the new notification endpoint:
credential responses carry a
notification_id, andcredential_accepted,credential_failureandcredential_deletednotifications are recorded in the audit log. - Issued SD-JWT VC credentials may carry a
statusclaim and be revoked or suspended, following the Token Status List specification: CAS publishes signed status list tokens, keeps entries in the ticket registry, offers anoidcVcStatusactuator endpoint to change a credential’s status, and as a verifier checks the status of its own credentials instead of refusing them. Turn it on withcas.authn.oidc.vc.issuer.status-list.enabled=true. - Credential requests and responses may be encrypted on top
of TLS, per OpenID4VCI 1.0: wallets encrypt requests to the encryption keys of the OpenID Connect keystore, published as
credential_request_encryption, and receive the response encrypted to the key they send incredential_response_encryption. Turn it on withcas.authn.oidc.vc.issuer.encryption.enabled=true; either direction may also be required. - The credential endpoint answers with the error codes of OpenID4VCI 1.0: an unpublished credential configuration is
unknown_credential_configurationinstead of the draft-eraunsupported_credential_type, and acredential_identifierthe token response did not return isunknown_credential_identifierinstead ofcredential_request_denied. - Once the token response returns
credential_identifiers, credential requests must usecredential_identifier, as OpenID4VCI 1.0 requires; acredential_configuration_idis then refused withinvalid_credential_request. Otherwise a request must name acredential_configuration_id, and no longer falls back to the configuration recorded on the access token. - Credential configurations may defer issuance (
deferred-issuance): credential requests are answered with atransaction_id, anoidcVcDeferredactuator endpoint approves or denies the pending transactions, and wallets collect the credentials from the new deferred credential endpoint, per OpenID4VCI 1.0. - Client attestations may be bound to short-lived challenges
from the new challenge endpoint, advertised as
challenge_endpoint, once turned on withcas.authn.oidc.client-attestation.challenge.enabled=true. With trust anchors set, clients may also use a DPoP proof in place of the attestation proof of possession (attest_jwt_client_auth_dpop). - Status list tokens carry an
aggregation_uri, and the new status list aggregation endpoint, advertised asstatus_list_aggregation_endpoint, lists all published status lists. - Wallets that ask for
application/jwtreceive the credential issuer metadata as signed metadata, per OpenID4VCI 1.0. - CAS can require DPoP proofs to carry a nonce it handed out, per RFC 9449:
turned on with
cas.authn.oidc.dpop.nonce.enabled=true, the token endpoint and the DPoP combined mode answer with400, and protected resources with401,use_dpop_nonceand a fresh nonce in theDPoP-Nonceheader. The OpenID4VCI nonce endpoint and the client attestation challenge endpoint hand out nonces as well. - Authorization codes can be bound to the client’s DPoP key,
per RFC 9449: with
dpop_jktat the authorization endpoint or in a pushed authorization request, or with aDPoPproof sent with the pushed authorization request. The token request must then carry a DPoP proof made with that key, or it is refused withinvalid_dpop_proof. - Clients can be required to use DPoP for every token request,
per RFC 9449, with
dpopBoundAccessTokensin the service definition ordpop_bound_access_tokensin a dynamic registration request. Polling with a device code and the JWT bearer grant now verify DPoP proofs too, and bind the tokens they issue. - A DPoP-bound access token is now accepted only in the
Authorizationheader with theDPoPscheme; sent as a bearer token or as a request parameter, it is refused withinvalid_token. Refresh tokens issued to public clients are bound to the DPoP key, per RFC 9449, and the stateless ticket registry keeps the DPoP binding of access and refresh tokens. - DPoP proofs are checked as RFC 9449 requires at every endpoint: a request
with more than one
DPoPheader, or with one that is not a JWT, is refused withinvalid_dpop_proof, which the token endpoint used to answer withinvalid_request. The profile and userinfo endpoints now answer a missing or invalid proof with401and aWWW-Authenticate: DPoP error="invalid_dpop_proof"challenge, as the credential endpoint did, and everyDPoPchallenge, Heimdall’s included, lists the algorithms accepted for DPoP proofs in itsalgsparameter. - Access tokens issued to a client that authenticates with mutual TLS
are bound to its certificate for every grant, not only
client_credentials, per RFC 8705. The profile, userinfo and credential endpoints now require the bound certificate, as Heimdall did, and answer401withinvalid_tokenwithout it; the stateless ticket registry keeps the binding. - Clients can be required to always receive certificate-bound access tokens,
per RFC 8705, with
tlsClientCertificateBoundAccessTokensin the service definition ortls_client_certificate_bound_access_tokensin a dynamic registration request: a token request from such a client that is not authenticated with mutual TLS is refused withinvalid_request. - CAS issues and verifies ISO/IEC 18013-5 mdocs,
such as a mobile driving licence, in the
mso_mdocformat of OpenID4VCI 1.0 and OpenID4VP 1.0, which HAIP also covers. A credential configuration with theMSO_MDOCformat names its document type withcas.authn.oidc.vc.issuer.credential-configurations[].doctype, and each claim may name its name space withcas.authn.oidc.vc.issuer.credential-configurations[].claims[].namespace. The issuer signs the claims’ digests and the wallet’s device key in a mobile security object with an elliptic curve key that carries an X.509 chain. A presented mdoc must be signed by one of CAS’s keys and its device signature must cover the session transcript of the request. CBOR and COSE are handled by the Authletecborlibrary, from the same vendor as the SD-JWT library CAS already uses. - Verifiable credentials issued as
jwt_vc_jsonorjwt_vc_json-ldno longer carry avctclaim, which belongs to SD-JWT VC; wallets such as walt.id classified such a credential as an SD-JWT VC and refused it as not matching the requested format.dc+sd-jwtcredentials keep theirvct.
Several improvements are now available for OpenID Connect with Verifiable Credentials:
- CAS may require specific principal attributes to determine eligibility before a credential offer transaction can be created for a principal.
- Pre-authorization codes are now also properly updated and removed from the ticket registry after a credential offer transaction is completed once the ticket is expired.
- Access tokens used for credential issuance are now bound to the credential configuration id when the token is minted; this is then enforced when the token is used to request credentials.
- Transaction-code requirements are enforced during token exchange.
- A pre-authorized code is now redeemed atomically, before the access token is minted, rather than being marked used afterwards. Previously two concurrent exchanges of the same code could each receive an access token, since the code was only updated once the token already existed. OpenID4VCI requires the code to be single use.
- Credential batch issuance is limited and capped at a predefined limit.
- OpenID Connect discovery now advertises
noneamongtoken_endpoint_auth_methods_supported, andnoneis understood as a client authentication method. CAS already accepted unauthenticated token requests for grants that carry their own proof of authorization, such as the OpenID4VCI pre-authorized code, but never said so; a client with no credentials had no way to discover that and would choose a method it could not satisfy. - OpenID Connect discovery now publishes
token_endpoint_auth_signing_alg_values_supported. RFC 8414 requires this wheneverprivate_key_jwtorclient_secret_jwtappears intoken_endpoint_auth_methods_supported, which CAS advertises by default, and clients that enforce the requirement previously refused to use the authorization server at all. The published list is configurable and, as the specification requires, excludesnone. - Credential issuance now speaks the finalized OpenID4VCI 1.0 wire format rather than draft 13. The credential request takes
proofs(plural) and acceptscredential_identifier; the credential response is acredentialsarray and no longer carriesformat. The separate batch credential endpoint is removed, since a batch is now one request carrying several proofs, and the issuer metadata advertisesbatch_credential_issuanceinstead ofbatch_credential_endpoint. The proof challenge is no longer returned from the token endpoint, and the nonce endpoint returnsc_noncealone. - The outcome of a verifiable presentation now reaches the relying party that asked for it. Previously the wallet was told whether its presentation verified and the transaction was deleted, leaving the relying party with no way to learn the result or the disclosed claims. A new
oidcVcPresentationResultendpoint, protected by the same client authentication as the request creation endpoint, reportspendinguntil the wallet answers and then delivers the outcome together with the disclosed claims, once. - Verifiable presentation requests are now delivered the way OpenID4VP requires. A request made under the
redirect_uriclient identifier prefix cannot be signed, and a request URI must serve a signed request object, so CAS now carries the whole authorization request by value in the wallet deep link and no longer offers a request URI in that mode. Settingcas.authn.oidc.vc.presentation.client-identifier-prefixtoX509_SAN_DNSsigns the request object and serves it by reference asapplication/oauth-authz-req+jwt. Previously CAS advertised a request URI that returned a plain JSON document. - The credential endpoint now behaves as an OAuth protected resource. A missing, unknown or expired access token is answered with
401and aWWW-Authenticatechallenge in the scheme the wallet used, rather than400 invalid_request. Other failures now carry the credential error codes OpenID4VCI defines, such asinvalid_credential_request,unsupported_credential_type,credential_request_denied,invalid_proofandinvalid_nonce, instead of a single opaqueinvalid_request. The split betweeninvalid_proofandinvalid_nonceis what lets a wallet tell a stale nonce, which it can recover from by fetching a new one, from a proof it cannot fix. - The authorization-code flow for verifiable credentials no longer ignores confidential clients or requests without PKCE. The response builder previously claimed the request only when the client had no secret and sent a code challenge; every other client had its
authorization_detailsquietly dropped, and the failure surfaced much later as a refusal at the credential endpoint. OpenID4VCI 1.0 only recommends PKCE and places no restriction on confidential clients. A credential the client cannot be granted is now reported asinvalid_authorization_detailsper RFC 9396 rather than dropped. - Credential types are now authorized per service. Previously the offer endpoint and the authorization-details filter only checked that a credential configuration id existed in
cas.authn.oidc.vc.issuer.credential-configurations, so any registered client could obtain any credential the deployment defined. An OpenID Connect service can now carry averifiableCredentialsPolicynaming the credential configuration ids it may obtain, enforced when an offer transaction is created, when authorization details become an authorization code, and again at the credential endpoint when the token is spent. A service with no policy, or a policy that names no credential types, may obtain everything the issuer publishes, so existing deployments are unaffected. - A service’s verifiable credentials policy may also narrow the algorithms its credentials are signed with, through
credentialSigningAlgValuesSupported. Like the credential types on the same policy it can only narrow: the effective set is the intersection with what the credential configuration advertises. - Verifiable credentials are no longer signed as if they were ID tokens. A credential was passed to the ID token signing service, so a client with
encryptIdTokenreceived a JWE rather than an SD-JWT,signIdToken=falseproduced an unsigned credential, and the algorithm came from the client’sidTokenSigningAlginstead of the advertisedcredential-signing-alg-values-supportedwhich CAS’s own verifier then rejected. Credentials are now always signed, never encrypted, and use the first advertised algorithm the issuer key can perform, with the advertised list enforced as the permitted set. - Issued credentials now honor a configurable lifetime per credential configuration,
cas.authn.oidc.vc.issuer.credential-configurations[].credential-validity, which defaults to thirty days. Previously every credential expired five minutes after issuance, which left it unusable by the time a wallet had stored it.
OpenID Connect with Verifiable Credentials now supports the authorization code flow. There are also significant changes in place to support more formats with additional bug fixes and enhancements, and an improved test suite to verify credential issuance. There is also initial support for OpenID Connect with Verifiable Presentations (OpenID4VP).
OAuth and OpenID Connect
OAuth and OpenID Connect security have been strengthened across several flows.
- Client assertions now require matching subject, issuer, and client identifiers.
- JWT IDs are tracked through the shared ticket registry to prevent replay across clustered deployments.
- Device and CIBA polling are bound to the authenticated client and completed poll-mode requests are consumed.
- Dynamic client key registrations are memory-bounded.
- Token exchanges are now authorized against the authenticated requesting client, and exchanged tokens cannot gain scopes beyond those granted to the subject token and allowed for the requesting client.
- Client secrets are now compared and enforced using a case sensitive strategy.
- Token introspection reports tokens from other clients as inactive, while revocation only affects the requesting client’s tokens and no longer reveals whether other tokens exist.
- DPoP-bound access tokens can now be spent. CAS answers the token request with
token_type: DPoPwhenever a proof accompanies it, but protected resources recognized theBearerauthorization scheme alone, so a client following RFC 9449 section 7.1 and presenting the token asAuthorization: DPoP ...was told the token was missing. The userinfo, JWKS registration and credential endpoints now accept either scheme. - DPoP proofs are now verified at protected resources through a single
validateProtectedResourceRequestoperation on the proof-of-possession validator. The credential endpoint previously accepted a DPoP-bound access token on presentation alone, which left the sender constraint doing nothing; it now answers a missing or invalid proof with401andWWW-Authenticate: DPoP error="invalid_dpop_proof". The userinfo endpoint keeps the same checks but no longer runs the token endpoint’s verifier over a resource request in addition to the correct one, and the resource-side check now includes the single-usejticheck. - DPoP proof validation no longer takes the client identifier from the unauthenticated
client_idrequest parameter. The identifier is resolved from the authenticated profile first, then from the access token, and only then from the request, which matters for grants that carry their own proof of authorization rather than client credentials, such as the OpenID4VCI pre-authorized code. Validation also no longer overwrites the profile identifier when that identifier belongs to the subject rather than the client. - Single-use checking for DPOP proofs is now enforced using the CAS ticket registry. Furthermore, DPOP requests are no longer treated as a form of client authentication and now sit on top of existing client authentication approaches such as client ID and client secret for confidential clients or PKCE.
- User-Managed Access resources and policies are now bound to both the authenticated client and resource owner. New resource registrations also receive non-sequential, server-assigned identifiers.
OAuth & OpenID Connect Client Secrets
OAuth and OIDC client applications may now define multiple client secrets,
allowing deployments to support secret expiration and smoother secret rotation. Existing
single-secret configurations remain compatible, while new configurations can
include additional secrets with expiration metadata so clients can transition
to new secrets without immediate disruption. Client secret rotation may be carried out using
a dedicated oauthClientSecrets actuator endpoint.
OpenID Connect Federation
Work on OpenID Connect Federation is now roughly finalized. A number of test scenarios and minor edge cases are still being worked on and should be resolved in the next few releases.
SAML2
SAML2 Identity Provider
SAML2 identity provider support receives the following improvements:
- Presented SAML2 authentication request signatures are now validated independently of the metadata signing requirement. Only a successfully validated signature may authorize an assertion consumer service URL that is not registered in service provider metadata.
- IdP-initiated SSO now also requires a caller-provided
shireto match a POST assertion consumer service registered in metadata. - Inbound encrypted SAML2 identifiers now use CAS’s matching IdP encryption certificate and private key for decryption.
- Signature algorithm inclusion and exclusion policies are now enforced consistently across all inbound SAML bindings.
- The
Addressfield of the SAML2SubjectConfirmationDatais now populated with the requester’s IP address when possible. - SAML2 metadata cache entries are now isolated by registered service and resolved without scanning unrelated cached resolvers.
- SAML2 authentication request session entries are updated per session and consumed after callback correlation, avoiding controller-wide serialization and retained replicated-session state.
- Storage-backed SAML2 metadata resolution now uses the requested entity ID to limit document lookups and resolver rebuilds.
- SAML2 SOAP attribute queries and artifact resolutions now require independently validated signatures, message freshness, destinations, and replay protection; artifact tickets are relying-party bound and consumed atomically.
- SAML2 single logout now validates request freshness and replay, fully authenticates and correlates logout responses, and generates actuator logout requests with the correct IdP issuer and service-provider destination.
- Identity provider metadata is now resolved atomically, so concurrent requests can no longer observe a metadata document, or the signing and encryption credentials that belong to it, that was installed on behalf of a different registered service.
- A service provider metadata backup file is no longer deleted ahead of a download attempt, and is used as a fallback when the remote metadata source or MDQ server cannot be reached, so a momentary outage there no longer takes the service offline.
- A service that defines a metadata signature location now fails to load its metadata when that certificate cannot be read, rather than loading the metadata unverified.
- Downloaded service provider metadata is written to its backup file in a single step, so services that share one metadata location can no longer read a partially written document.
- The JDBC and MongoDB identity provider metadata stores now scope the global document lookup to the global owner, so a service’s own document and keys can no longer answer in its place.
- Metadata query requests now ask for
application/samlmetadata+xmlvia theAcceptheader, as the SAML profile for the Metadata Query Protocol requires, and are issued through the CAS HTTP client so they use the deployment’s TLS trust store and hostname verifier. - The SAML2 metadata health indicator answers from the local metadata backup copy where one exists, instead of reaching out to every service’s metadata host on every health check.
- SAML2 metadata user interface information is resolved once per login attempt rather than twice, and dynamically-resolved entities are cached, so rendering the login page no longer issues a metadata query per request.
- Outbound HTTP calls now reuse their underlying HTTP client and its connection pool instead of building and discarding one per request, which recovers connection reuse and stops the server accumulating open sockets over time.
- Service names can no longer resolve per-service identity provider metadata directories outside the configured metadata root. Explicit
idpMetadataLocationsettings remain supported. - HTTP response buffering now limits downloads, including decompressed bodies, to 256 MiB by default. The limit can be changed with the
org.apereo.cas.util.http.HttpRequestUtils.maximumResponseSizesystem property or overridden per request.
CAS & Other Protocols
CAS Protocol
- The CAS
1.0validation response is line-delimited and carries no escaping mechanism. Principal identifiers and rendered attribute lines are now stripped of line breaks, so a value that contains a newline can no longer forge additional lines in the response. - A proxy-granting ticket is now issued only after the service ticket, the validation specification and the
authentication context have all been accepted. The
pgtUrlcallback used to be contacted and the proxy-granting ticket minted before any of those checks ran, so presenting a leaked service ticket could drive an outbound request and leave an unused proxy-granting ticket behind even though validation went on to fail. - Validation responses now use the protocol’s own error codes for two cases that previously reported something else.
A ticket that fails the validation specification without a
renewrequest is reported asINVALID_TICKET_SPECrather thanINVALID_TICKET, and an unexpected failure during validation is reported asINTERNAL_ERRORrather thanINVALID_REQUEST, which stays reserved for a request that is missing required parameters. A ticket that did not come from an initial login whilerenewwas requested continues to be reported asINVALID_TICKET, as the protocol specifies. - Attribute names are now sanitized into valid XML names before they are rendered as elements in the CAS
2.0/3.0validation response, and proxy URLs in<cas:proxy>are XML-escaped. Previously an attribute name was only stripped of spaces and a proxy URL was written out verbatim, so either could carry markup or quoting characters into the response. Note that a name containing characters that are not legal in an XML name — a colon, for instance — now renders with those characters replaced by an underscore; such a name previously produced a response that was not well-formed XML. - The SAML
1.1validation response now setsRecipientto the service URL the response is intended for, and reservesInResponseTofor theRequestIDof the request being answered, omitting it when the caller did not send one.InResponseTopreviously carried the hostname of theTARGETservice unless aRequestIDhappened to be present, andRecipientwas never set at all, so a relying party had no way to bind the response to its own request or endpoint. - The
renewparameter presented to the CAS protocol validation endpoints is now evaluated per request. Validation specifications are shared components and the requested value used to be assigned onto them for the duration of a request, which allowed a concurrent validation request to reset it. A service ticket that was not issued from a new login can no longer satisfy arenew=truevalidation because of another request that happened to be in flight at the same time.
Authorization
Heimdall AuthZEN
Heimdall AuthZEN support is reworked to follow the AuthZEN Authorization API 1.0 specification:
- The AuthZEN
resource.idnow identifies the resource instance rather than a policy namespace. AuthZEN requests are matched against resources in all namespaces by their newresourceType,actionsand optionalresourceIdPatternfields, and all matching resources must grant access. Existing AuthZEN resources must define these fields, or AuthZEN requests are denied; requests to/heimdall/authorizeare unaffected. - Evaluated denials return
200with"decision": false, malformed requests400, and failed caller authentication401. TheX-Request-IDheader is echoed. - Tokens must be issued to a registered OAuth or OpenID Connect application whose access strategy allows access, and
a new Heimdall access strategy can prevent an application from calling Heimdall. DPoP-bound and certificate-bound tokens
now require their proof. On the AuthZEN endpoint,
Basiccredentials are theclient_id:client_secretof a registered application rather than CAS user credentials. - JWT bearer assertions must carry
jtiandiatclaims, are accepted once, and may not live longer thancas.heimdall.jwt-assertion-max-lifetime(five minutes by default). - Failed caller authentication on
/heimdall/authorizenow returns401instead of403. Failed caller authentication on both Heimdall endpoints is subject to authentication throttling. - A resource with no policies now denies access instead of granting it, and the REST policy no longer sends the resource’s policies to its endpoint.
- JDBC policies use a shared connection pool, registered as an application context bean and optionally named via
dataSourceName, instead of opening a new database connection for every decision. Queries time out afterqueryTimeout(five seconds by default). Policies are evaluated in order rather than on the shared thread pool. - AuthZEN subjects are resolved from CAS attribute repositories only for the
usersubject type. Required and rejected attribute policies accept qualified names such assubject.properties.department,resource.properties.owner,action.properties.methodandcontext.channel. HTTP request headers are no longer added to the AuthZEN request context; on/heimdall/authorizethey no longer override body entries or include protocol headers. - A resource that does not set
enforceAllPoliciesis now granted when any one of its policies grants access, as documented; previously every policy had to grant. SetenforceAllPoliciestotrueon resources that rely on the old behavior. In that mode, a policy that fails with an error no longer prevents a later policy from granting access. - Heimdall supports the AuthZEN access evaluations API at
/heimdall/authzen/evaluations, with theexecute_all,deny_on_first_denyandpermit_on_first_permitsemantics. - Heimdall publishes AuthZEN policy decision point metadata at
/heimdall/.well-known/authzen-configuration; the well-known location defined by the specification needs a rewrite rule. - Denied AuthZEN decisions carry a decision context
with a
reasoncode. - JDBC and OpenFGA policies receive the AuthZEN subject, resource and action. Palantir can edit the AuthZEN fields of a resource and configure the Heimdall access strategy.
Attributes & Consent
Attribute Consent
- Consent decisions are now assigned a unique identifier when they are created.
Only the JDBC store generated one; every other store recorded the decision with an identifier of
0, which is the key those stores use. LDAP and Redis therefore kept a single decision per user, and MongoDB and DynamoDB kept a single decision for the entire deployment, each new consent silently replacing the one before it. Decisions recorded by earlier versions continue to be honored. - DynamoDB consent lookups now match the service and the decision identifier for equality. They were matched as greater-than-or-equal, so consent granted to one service could answer the check for another, and revoking a single decision removed every decision whose identifier sorted at or above it.
- Redis consent lookups now treat the principal as a literal value.
Glob characters such as
*and?, and the:separator itself, were interpolated into the scan pattern, so a principal identifier that contained them could read and delete decisions that belong to other users. - Revoking consent now removes every decision it should. Against the JDBC store, revoking all decisions for a user removed nothing at all once the user had
more than one, and when several stores are configured together only the first store that reported success was
asked to delete. The REST store now sends the decision identifier as
decisionId, the parameter its documented contract defines; it previously sentid, which a conforming server reads as a request to revoke everything for that user. - A consent decision that cannot be deciphered no longer fails the login. Rotating the
cas.consent.core.cryptokeys, or a node that does not have them, produced an error that propagated out of the login flow; such a decision now counts as a mismatch, so the user is asked to consent again and the record is replaced using the current keys. - Reminder options submitted with a consent decision are now validated. A missing or unparsable value falls back on the configured
default, and a reminder that cannot be applied to a date, such as a negative amount or a time unit of
FOREVER, is rejected rather than stored and then failing every subsequent login for that user. - Consent decisions record their creation date in UTC, so a decision’s age is the same on every CAS node whatever time zone each node runs in. Dates shown on the account profile screen are labeled as UTC.
- Consent is now evaluated once per request. The activation strategy returns
the full consent query result, carrying the decision and the consentable attributes, and the login flow reuses it rather than
evaluating the attribute release policy and reading the consent store a second time to render the consent screen. Note that a Groovy
activation script must now return a
ConsentQueryResultinstead of a boolean.
Attribute Definition Dependencies
Attribute definitions may now declare dependencies on other attribute definitions. When an attribute is resolved, its declared dependencies are resolved first and their results are made available during resolution, allowing definitions to build on values produced by other definitions in a predictable, reusable way.
Tickets & Sessions
Stateless Ticket Registry
With the stateless ticket registry, the ticket-granting ticket is now carried by the
ticket-granting cookie, like with any other ticket registry, instead of being kept in browser storage. The single sign-on session
therefore follows the ticket-granting cookie settings, and login pages no longer render a browser storage page before the login form.
The cas.ticket.registry.stateless.storage-type setting no longer applies and is removed.
Stateless tickets now use a versioned format in which every field is length-prefixed, so values that contain separator characters, like distinguished names, round-trip correctly. Other changes:
- Tickets are compressed only when that makes them smaller.
- Each ticket is bound to the ticket type it was issued as, so changing its prefix (for example, presenting an access token as a refresh token, or a proxy ticket as a proxy-granting ticket) no longer yields a valid ticket.
- OAuth response and grant types are stored by name, so reordering them in a later release does not change existing tokens.
- Expanding a ticket no longer goes through the ticket factories, which skips id generation and service registry lookups.
- An expanded ticket keeps the id it was looked up by, and its authentication keeps the original authentication date.
- The ticket-granting ticket no longer carries its own id or the tickets it has granted, which the stateless registry does not track.
- The ticket-granting ticket no longer carries principal attributes. They are fetched from attribute repositories again each time the ticket-granting ticket is read, the same way they already were during ticket validation, which keeps the ticket-granting cookie small. Attributes that only authentication handlers produce, such as claims from Duo Security or delegated authentication, are no longer available to single sign-on decisions unless an attribute repository produces them as well.
- The ticket-granting ticket keeps only its authentication and is created through the ticket-granting ticket factory when read. It expires at the end of its maximum lifetime; an idle timeout configured for it is not enforced.
- Verifiable credentials can be issued: OAuth authorization codes keep their authorization details, access tokens keep their credential configurations and authorization details, and the credential offer and nonce endpoints hand out the stored ids. The pre-authorized code and nonces are not single use there. Verifiable presentations are not supported.
- The OAuth client credentials and resource owner password grants issue access tokens; they failed while recording the use of the ticket-granting ticket they create, which the stateless registry hands back in encoded form.
The ticket-granting cookie can now be encrypted without being signed, using cas.tgc.crypto.signing-enabled=false (signing stays
on while a signing key is defined). The cookie encryption is authenticated, so this keeps tamper detection and makes the cookie
about a quarter smaller. This is recommended with the stateless ticket registry, where the cookie carries the
ticket-granting ticket and can otherwise exceed the 4096 bytes browsers accept, for example after Duo Security multifactor
authentication. See the stateless ticket registry documentation for details.
MongoDB Integrations
- A configuration refresh no longer drops MongoDB collections.
drop-collectionanddrop-indexesdescribe what happens at startup, but a refresh of the MongoDB ticket registry or service registry re-ran them and destroyed live tickets or registered services. Collection and index creation still runs on refresh, as it is repeatable. - The MongoDB ticket registry no longer reports a storage failure as a missing ticket. Adding, updating or fetching a ticket now surfaces the underlying failure instead of logging it and carrying on, so a database outage, an oversized document or a ticket definition missing from the catalog can no longer look like a successful login followed by an invalid ticket.
- Result limits are now applied by MongoDB rather than after the fact, so paging through tickets and querying the ticket registry no longer fetch and deserialize whole collections only to discard them. Cursors opened across several collections are now closed even when the caller stops reading early, and collections shared by more than one ticket definition are read and counted once.
- An
IDX_PRINCIPALindex is now created on every ticket collection. Queries by principal, such as removing all tickets issued to a user, run against all collections while only the ticket-granting ticket collection carried the index. - Querying single sign-on sessions by an attribute whose name contains a dot now matches. Such names are escaped when the document is written, and the query did not apply the same escaping.
Interface & Accounts
Views and Themes
- The REST-based view resolver no longer forwards credential-bearing request
headers, such as
Cookie,AuthorizationandProxy-Authorization, to the external template endpoint. - Every resolved theme name is now matched against a real
theme definition before it is honored, no matter how the
name was chosen. A name without a matching
[theme].propertiesfile, either at the root of a configured template prefix or at the root of the classpath, is ignored and the next resolver in the chain applies. This closes a gap where names taken from a request header, cookie or session value were used without any check, and it makes the Groovy and REST theme sources hold to the same contract as every other theme: a script or endpoint that returns a name with no theme definition behind it now falls back to the default theme rather than selecting a theme that does not exist. - A service theme is now resolved once per request rather than once per template lookup, so service resolution, access strategy evaluation and theme file lookups no longer repeat for every fragment rendered on a page.
- Thymeleaf template caching now takes effect. The chaining template resolver previously declared every resolution
non-cacheable regardless of
spring.thymeleaf.cache, so each page and each fragment was re-resolved and re-parsed on every request. Resolutions are now cached per the configured setting, except for those produced by a theme-aware resolver: those map one template name to different files depending on the request’s theme, and Thymeleaf’s cache key does not carry the theme, so they remain non-cacheable. Deployments with no themed template overrides cache every template; deployments with themed overrides cache everything except the overridden templates.
Interrupt Notifications
- Following a link on a blocking interrupt no longer records the interrupt as acknowledged, and a blocking response is never skipped by interrupt tracking. Tracking cookies are now bound to the principal; cookies issued by earlier versions are ignored, so users may see an acknowledged interrupt once more.
- Interrupt notifications no longer render during passive requests (CAS
gateway, OpenID Connectprompt=none, SAML2IsPassive); CAS returns to the application without a ticket instead, as required by the respective specifications. - JSON interrupt notifications now load their policies once and reload file changes atomically, so concurrent logins can no longer skip an interrupt while the file is being re-read.
- REST interrupt notifications now read the response payload only for successful status codes; error responses no longer interrupt every login with a generic message.
Acceptable Usage Policy & Multitenancy
Acceptable Usage Policy (AUP) support has been extended to support multitenancy. A number of storage mechanisms are now available to support multitenancy, specifically MongoDb and more will be added in the future.
Platform & Operations
OpenRewrite Recipes
CAS continues to produce and publish OpenRewrite recipes that allow the project to upgrade installations in place from one version to the next. See this guide to learn more.
Graal VM Native Images
A CAS server installation and deployment process can be tuned to build and run as a Graal VM native image. We continue to polish native runtime hints. The collection of end-to-end browser tests based on Puppeteer have selectively switched to build and verify Graal VM native images and we plan to extend the coverage to all such scenarios in the coming releases.
MCP Server
CAS can now act as a Model Context Protocol (MCP) server, so AI assistants and agents
can examine a CAS deployment through read-only tools. They find the registered service that matches an application URL,
evaluate whether a person may access an application and explain a denial, preview the username and attributes released to an
application, determine which multifactor authentication provider is required and why, preview single logout notifications,
search registered services, look up single sign-on sessions by person or attribute, tickets, tenants, audit records and
people along with their resolved attributes. Tools that examine optional features are
offered only when that feature is included in the build; these include attribute consent decisions, SAML2 service provider
metadata resolution with certificate expiration dates, and OpenID Connect client registrations and keystore keys. The server uses the Streamable HTTP transport at /cas/mcp; every request must be authenticated with the
same user stores that protect actuator endpoints, and tool calls require the MCP_ADMIN role.
The server can also return the recorded authentication events of a person, with the client IP address, user agent and
location of each, and, when those features are included in the build, the devices a person trusts for multifactor
authentication and the accounts a person may impersonate. Device fingerprints are never returned.
It can also preview the interrupt notification a person would see after logging into an application, and score how
risky a login attempt from a given client IP address and user agent would be. It lists the one-time password devices
and WebAuthn security keys or passkeys a person has registered, without secret keys, codes or key material, and the
multifactor authentication trigger evaluation now accepts the client IP address, user agent and location of the login
attempt so that adaptive triggers can be checked. Passwordless accounts (with masked contact details), delegated identity
providers and Heimdall resources can be looked up as well, and the CAS settings catalog is offered as the MCP resource
cas://settings/{name}, which describes a setting without revealing its value; the resource capability is now turned on with
spring.ai.mcp.server.capabilities.resource=true. A searchSettings tool finds settings by name or
description. Tickets are now summarized rather than returned in full, so attribute values no longer leave CAS through the
MCP server, and every MCP tool call and resource read is recorded in the audit log under the MCP user who made it.
Single sign-on sessions are identified by a one-way session reference rather than their ticket-granting ticket id, and service
definitions are returned with client secrets, passwords and keys redacted.
Agents can now start with describeDeployment, which reports the CAS version, enabled features, registries, authentication
handlers, attribute repositories and active MCP tool groups, and use explainLogin to walk through access, risk, multifactor
authentication, interrupts and attribute release for a person and an application in one call. validateServiceDefinition
checks a proposed service definition and which service would win for given URLs without saving it, findThrottledLogins
shows throttled login attempts, and findAttributeSources and findAttributeDefinitions show which attribute repository
returns which attribute and how attribute definitions transform them. Tools now publish an output schema and return
structured content, registered services are offered as the resource cas://services/{id}, and setting names and service ids
can be completed by clients, with spring.ai.mcp.server.capabilities.completion=true.
The documentation now explains how to let browser-based MCP clients reach the server through CAS CORS support.
JMX Management
JMX management now includes service reload and lookup, ticket and session counts with filtered listings, expired-ticket cleanup, authentication and MFA diagnostics, and principal attribute cache invalidation. Service listings also release backend resources correctly.
Palantir
The OpenID Connect service wizard now includes an advanced verifiable credentials policy section for allowed credential types and signing algorithms.
The service history endpoint can now restore a selected registered-service revision to the service registry and live service cache, while retaining its history. Palantir offers a Restore Version action in the revision table’s context menu under View Change History.
Groovy Scripting
- Groovy scripts no longer abandon an execution when the script is already busy on another thread. Previously a script that stayed busy for more than five seconds caused queued executions to return no result at all, which could silently skip a multifactor authentication trigger or an acceptable usage policy check. Executions now wait for their turn.
- Bindings assigned to an inline
groovy { ... }script are now scoped to the assigning thread and to a single execution, so variables belonging to one request can no longer be observed by the next execution of that script or by an unrelated script. - An inline
groovy { ... }script that is executed without variables, such as a scripted multifactor authentication trigger, now keeps working past its first execution. The script’s variables were previously reset to an immutable map once it had run, so every later execution failed internally and produced no result. - OpenID Connect claim mappings defined as scripts no longer discard the shared Groovy script cache after every claim. Compiled scripts, and the reloading of scripts as their files change, are now retained across token requests.
- Inline scripts used by allowed attributes, pattern-matching attribute transformations and service access strategy required attributes are compiled once and served from the script cache. Previously each of these compiled a fresh script for every attribute on every request.
- The surrogate access strategy and Groovy SAML2 metadata resolution no longer compile their script, and register a new file watcher, on every request.
- The multifactor authentication principal attribute predicate compiles its Groovy class once and recompiles it only after the file changes, instead of on every authentication attempt.
- A Groovy script that fails to compile, or that throws while running, now reports that failure to the CAS component which asked for it rather than reporting no result at all. Components designed to carry on without a script result, such as mapped attribute release and the Groovy username provider, keep their existing behavior.
- A Groovy access strategy activation criteria that produces no result now fails the request with an explanation. Previously it raised an unexplained error, and treating the missing result as an inactive criteria would have granted access without evaluating the service’s required attributes.
Palantir Admin Dashboard
Inlined table buttons in Palantir Admin Dashboard are replaced with proper context menus triggered by right clicks. The configuration tab is also extended to display cached scripted resources with the ability to either remove or recompute the cache entry.
Furthermore, Heimdall authorization policies can now be created, edited and removed from the Palantir Admin Dashboard. There is also dedicated simulation support to experiment with authorization requests.
Configuration for almost all attribute repositories can also be viewed in better detail. The dashboard is also able to register new attribute repositories for LDAP, JDBC and Stub repositories.
Finally, fields that do support inline Groovy scripts are also allowed to better receive their value from a dedicated editor.
Palantir User vs. Admin Roles
The Palantir Admin Dashboard now supports basic user roles and permissions.
By default, all authenticated users are assigned a ROLE_USER role/authority. To access critical functionality as an admin,
you will need to resolve and release a role attribute to Palantir with a value of ADMIN or ROLE_ADMIN if you are
accessing Palantir via external CAS authentication, or your configuration needs to assign the authenticated user role
via spring.security.user.roles=ADMIN.
At this moment, all Palantir functionality is disabled and hidden for non-admin users, except for the ability to manage the list of registered applications.
Cluster Topology
A new clusterTopology actuator endpoint is available to report on the current cluster topology
and the status of each node in the cluster, particularly relevant when CAS is running in high-availability mode.
Cluster topology support is available for the following features:
- MongoDb Ticket Registry
- Redis Ticket Registry
- Hazelcast Ticket Registry
- Apache Ignite Ticket Registry
- Apache Kafka Ticket Registry
- Apache Pulsar Ticket Registry
- Apache Geode Ticket Registry
- AMQP Ticket Registry
This capability is also supported and available for the Palantir Admin Dashboard.
Documentation
Documentation
The CAS documentation site has received a visual and functional overhaul. Notable changes include:
- A refreshed theme with light and dark modes, a bold grotesque typeface for headings, a monospaced sidebar, and quiet icons for recurring sections such as configuration, actuator endpoints and troubleshooting.
- Configuration settings are presented as a searchable, filterable reference list. Each setting can be expanded
to show its description, type, default value and deprecation status, and copied as
.properties, YAML or environment variables. The chosen format is remembered across pages. - Actuator endpoints are grouped by endpoint, with each operation listing its parameters, response and a working
curlexample inline. Instructions to enable, expose and secure the endpoint along with related settings and troubleshooting notes are shown once per endpoint rather than once per operation, which makes pages considerably lighter. - The configuration properties search is rewritten. It matches setting names regardless
of how they are written (property names, environment variables including indexed ones such as
CAS_AUTHN_LDAP_0_LDAP_URL, or pasted assignments), supports exact name matches, searches names or descriptions, filters CAS or third-party and deprecated settings, and keeps the search in the page address so results can be shared. - Pressing Shift twice on any documentation page opens a quick search for configuration settings.
- Feature toggles are grouped by area and can be searched or filtered
to those that are off by default. Each feature can be expanded to show its modules, their default state, the auto-configuration
classes they control and a link to the relevant documentation, and every toggle can be copied as
.properties, YAML or environment variables. - The documentation build and validation time is significantly reduced and changes are now published up to
75%faster. External links are checked on a weekly schedule rather than on every change, and broken external links no longer block publishing. - Every page has its own title, overview pages carry a written description, and other pages describe themselves in search results with their first paragraph rather than a shared boilerplate sentence.
- The Versions menu opens the current page in the selected release, and lists development and the releases still under maintenance.
- Comparison tables help choose between ticket registries, service registries, multifactor providers and protocols.
- Text in setting lists and feature toggles meets contrast requirements in both themes, the dependency tabs are announced correctly by screen readers, and third-party scripts and stylesheets are pinned and loaded with integrity checks.
- On phones, the site header scrolls away to leave only the navigation bar, and the development notice shrinks to a single line, so the page heading appears on the first screen.
- Heimdall and multitenancy documentation is split into focused pages with their own menu entries.
- Every page opens with a short summary, which also serves as its description in search results.
- Docker installation names an explicit image tag, and Getting Started points to it for a quick local trial.
- A short Quick Start recipe, featured on the home page and at the top of Getting Started, takes a new deployer from the Docker image to a configured
overlay with a registered application and an optional LDAP connection. Every setting it mentions is a link: clicking one
opens its description, default value, module and
.properties, YAML or environment variable form in place, without leaving the page. Settings mentioned throughout the documentation and release notes are marked the same way, and the Shift Shift search shows a setting’s details directly instead of moving to the configuration catalog. Features are chosen before the overlay is generated: the Initializr command, pinned to the same release as the Docker preview, carries the selected modules, and the build step assembles the overlay’sdependenciesblock and the completecas.propertiesfor the selection. The selection now survives the browser’s Back button, and WebAuthn runs on the samehttp://localhost:8080server as every other recipe. New recipes cover Groovy scripting and the Hazelcast ticket registry, and interrupt notifications now come from a Groovy script. The wizard keeps the selection consistent: interrupt notifications bring in Groovy, and Redis and Hazelcast replace each other. - The site loads two web fonts instead of four; code and the sidebar use the system monospace font. Images hosted on third-party sites are removed, and the logout session example is now a table.
- Release notes open with a digest of each release candidate: what to review before upgrading, the highlights and, where a release has screenshots, a spotlight. Every change below it is tagged by type and grouped by area, and can be searched and filtered; the overview lists every release candidate with its summary, and the combined release candidate page gathers every change from every release candidate on one page, tagged with the release candidate it shipped in.
- The documentation sidebar can be filtered by page name, groups its sections under six headings (start, sign-in, protocols and applications, identity data and access, experience and operations), keeps only the current path open with its parent sections pinned at the top while scrolling, and opens a section’s overview from the section name itself instead of a separate Overview entry.
Build & Project
Testing Strategy
The collection of end-to-end browser tests based on Puppeteer continue to grow to cover more use cases
and scenarios. At the moment, total number of jobs stands at approximately 572 distinct scenarios. The overall
test coverage of the CAS codebase is approximately 94%.
The collection of end-to-end browser tests based on Puppeteer continue to grow to cover more use cases
and scenarios. At the moment, total number of jobs stands at approximately 609 distinct scenarios. The overall
test coverage of the CAS codebase is approximately 94%.
The collection of end-to-end browser tests based on Puppeteer continue to grow to cover more use cases
and scenarios. At the moment, total number of jobs stands at approximately 556 distinct scenarios. The overall
test coverage of the CAS codebase is approximately 94%.
JSpecify & NullAway
CAS codebase is now annotated with JSpecify annotations to indicate nullness contracts on method parameters, return types and fields. We will gradually extend the coverage of such annotations across the entire codebase in future releases and will integrate the Gradle build tool with tools such as NullAway to prevent nullness contract violations during compile time.
Spring Boot 4.2
CAS is now built on top of Spring Boot 4.2.x. This is an in-progress ongoing minor platform upgrade that
affects almost all aspects of the codebase including many of the third-party core libraries used by CAS
as well as some CAS functionality.
Please refer to the Spring Boot Wiki for more information on the changes and updates in this release.
CAS is now built on top of Spring Boot 4.2.x. This is an in-progress ongoing minor platform upgrade that
affects almost all aspects of the codebase including many of the third-party core libraries used by CAS
as well as some CAS functionality.
Please refer to the Spring Boot Wiki for more information on the changes and updates in this release. The biggest change to CAS would be support for AMQP 1.0.
REST password management, trusted-device storage, Clickatell SMS and Spring Boot Admin registration now use Spring’s
RestClient in place of deprecated RestTemplate APIs.
CAS is now built on top of Spring Boot 4.2.x. This is an in-progress ongoing minor platform upgrade that
affects almost all aspects of the codebase including many of the third-party core libraries used by CAS
as well as some CAS functionality.
Please refer to the Spring Boot Wiki for more information on the changes and updates in this release. The biggest change to CAS would be support for AMQP 1.0.
Java 27
CAS may be built and run using Java 27 and the build process has been updated to use
the latest Java 27 features and capabilities. Please note that this is only a preparatory step for future
releases and the baseline requirement will remain as it was.
Gradle 9.8
CAS is now built with Gradle 9.8 and the build process has been updated to use the
latest Gradle features and capabilities.
Gradle 9.7
CAS is now built with Gradle 9.7 and the build process has been updated to use the latest Gradle features and capabilities.
Other Changes
Authentication
- RC3 Hazelcast maps of all CAS modules no longer evict entries to make room by default:
cas.ticket.registry.hazelcast.cluster.core.eviction-policyand its counterparts now default toNONE, since evicting a ticket or session that is still valid logs its user out without notice; entries leave when they expire. After a network split heals, the copy of an entry that was updated last now wins (cas.ticket.registry.hazelcast.cluster.core.map-merge-policydefaults toLATEST_UPDATE), so changes made on either side, such as applications added to a session, are kept. Choosing a merge policy that compares update, access or expiration times or hits no longer stops the Hazelcast member from starting. - RC3 A CAS Hazelcast member with TCP/IP and multicast turned off no longer joins other Hazelcast members on the network through Hazelcast’s auto-detection, which fell back to multicast; it runs on its own, and other members can join it only through the TCP/IP, multicast or discovery settings CAS exposes. Removing failed login records of authentication throttling with Hazelcast no longer fails when some of the records are kept on another member of the cluster.
- RC3 Delegated authentication no longer fails intermittently when concurrent requests reach an identity provider that is still being initialized, typically right after startup. Such requests now wait for the initialization in progress instead of failing, which also affects SAML2 identity providers when building SAML2 responses, metadata and logout requests.
- RC3 CAS now logs a warning when a cookie it writes, such as the ticket-granting cookie, is larger than the 4 KB that browsers are guaranteed to accept.
- RC3 With the acceptable usage policy kept in MongoDb or Redis, an accepted policy is remembered at later logins; the decision used to be stored but never read back, so the policy was asked for at every login. MongoDb now creates the user’s document when there is none.
- RC1 When removing cookies, particularly during logout, the existing cookie value is no longer echoed back for remove operations.
Multifactor & Passkeys
- RC3 WebAuthn registration and login pages no longer show a broken image when the device has no icon in its attestation metadata, and fall back to the credential nickname for the device name.
- RC3 WebAuthn pages now report browsers that cannot run them; the support check was never applied, and it no longer requires a platform authenticator.
- RC3 WebAuthn authentication pages now send the CSRF token rendered by CAS instead of reading it from the
XSRF-TOKENcookie, which failed with403whenever the page could not read that cookie. - RC3 Browser storage used by Duo Security and the SAML2 identity provider now falls back to cookies when the browser cannot use local or session storage.
- RC3 Google Authenticator devices can only be removed or confirmed by the user that owns them; a device id that belongs to another user is now refused.
- RC3 Google Authenticator device repositories: removing a device or a user from DynamoDB no longer removes other records with it; registering or updating a device in LDAP no longer replaces the user’s other devices; and removing or confirming a device with JPA works again and no longer re-encrypts the device secret when encryption is enabled.
- RC3 Google Authenticator device removal and confirmation now expire if not completed within a few minutes of the code being verified.
The REST device repository now sends deletions as
DELETEand includes the deviceidandpropertieswhen saving. A REST or JSON repository that cannot be read now fails the login instead of offering device registration, and LDAP removes the right device when several users’ device ids share leading digits. - RC3 More Google Authenticator repository fixes:
MongoDB no longer treats usernames that differ
only by accents (such as
joseandjosé) as the same user; the tenant of a device is kept when it is registered and stored, including in DynamoDB and JPA; DynamoDB stores encrypted scratch codes and an emptied list of scratch codes; saving a device no longer alters the object passed in; and every repository now counts devices rather than users and replaces a device saved twice instead of storing a duplicate. - RC3 Google Authenticator devices now record when they were last used, in every repository. Updating a device that is not stored now adds it in every repository. A code used to verify a device removal or confirmation is no longer restored afterwards. A device id that is not a number is refused instead of failing with a server error. The JSON repository replaces its file in one atomic step and is documented as single-node.
- RC3 Faster Google Authenticator lookups: DynamoDB
reads a device by key and finds a user’s devices through a new
useridIndexglobal secondary index instead of scanning the table (CAS adds the index at startup; create it yourself if table creation on startup is turned off), and counts without reading records back. MongoDB creates an index on usernames with the collation its lookups use. - RC3 Google Authenticator LDAP device lookups by id no
longer read every entry, and saving or removing a device no longer decrypts and re-encrypts the user’s other devices.
Redis loads all devices with batched
MGETinstead of one request per device, and saving a device no longer decrypts the user’s other devices. - RC3 The Google Authenticator JPA repository can now save a device whose id is not already in the database, such as one imported through the actuator endpoint; the database assigns it a new id. Removing a user’s devices is now a single bulk delete.
- RC3 WebAuthn falls back to the cached FIDO metadata BLOB when downloading a fresh one fails at startup, for example when the FIDO metadata service rate-limits the request, instead of failing to start.
- RC3 Each WebAuthn storage module (JPA, MongoDb, Redis, LDAP, DynamoDb, REST) can now be switched off with
CasFeatureModule.WebAuthn.<module>.enabled=false, as the Google Authenticator, trusted device and consent storage modules already could, so that several can ship in one build while only one keeps device registrations. - RC3 With trusted devices kept in Redis, logins continue with multifactor authentication while Redis cannot be reached, as with the JDBC storage, instead of failing with a server error.
- RC1 Multifactor authentication may also be activated using SAML2 metadata entity attributes.
OAuth & OpenID Connect
- RC3 OAuth and OpenID Connect requests find the application definition for a
client_idthrough the service index instead of examining every definition, falling back to examining them when the index has no match. - RC3 Listing application definitions, for example through the
registeredServicesactuator endpoint or for the OpenID Connect JWKS endpoint, no longer includes expired definitions that are set to be deleted when they expire. A definition that is renewed and expires again notifies its contacts again. - RC3 Reloading application definitions no longer empties the cache and the service index before filling them again. New and updated definitions are added first and removed ones are dropped last, so requests that arrive during a reload no longer miss an application, match a broader definition by mistake, or fail to find an OAuth or OpenID Connect client.
- RC3 Matching a request to an application definition no longer sorts every cached definition on each lookup, and listing
definitions, as the OpenID Connect JWKS endpoint and the
registeredServicesactuator endpoint do, no longer re-indexes every cached definition. - RC3 An OAuth/OpenID Connect token request now saves the user’s single sign-on session once instead of once per token it issues; exchanging an authorization code for an access and a refresh token used to write it three times.
- RC3 The
at_hashclaim of an OpenID Connect ID token is now computed over the access token returned in the same response. CAS used to build and sign the access token a second time for the hash, which doubled the work for JWT access tokens and produced anat_hashthat did not match when the access token was signed with ES* or PS* algorithms, encrypted, or requested as a JWT through token exchange. - RC3 Reading the OpenID Connect keystore from a file, as every request to the JWKS endpoint does, no longer fails an attempt to parse the file path as an inline keystore first, and the keystore generated event is published only when CAS actually generates a new keystore instead of on every read.
- RC3 JWT access tokens, JWT response modes and other tokens signed or encrypted with keys defined per application now reuse the prepared signing and encryption keys instead of parsing them again for every token issued or read; a changed application definition or a rotated key is picked up the first time it is used. Each such token also looks up the application’s keys once instead of three times; for an OpenID Connect application, a look-up reads its keystore and serializes the keys.
- RC3 OpenID Connect ID tokens issued for
response_type=id_tokenno longer carry anat_hashclaim, since no access token is issued with them. Forresponse_type=id_token token, theat_hashclaim is now computed over the access token returned in the same response; for JWT access tokens it used to be computed over a different token and did not match. - RC3 An OAuth/OpenID Connect authorization code is now redeemed by removing it from the ticket registry before any token is issued, and only the request that removes it receives tokens. Two token requests presenting the same code at the same moment could previously both receive tokens. The code is also no longer written back to the ticket registry right before it is redeemed.
- RC3 An approved device code and a
CIBA request in poll or ping mode are now redeemed the same way: only
the token request that removes them from the ticket registry receives tokens. Two polls arriving at the same moment could
previously both receive tokens. A CIBA request is now removed before its tokens are issued rather than after, and a
request that lost the race gets
invalid_grant, as CIBA Core 1.0 requires once theauth_req_idhas been redeemed. - RC3 The OpenID Connect JWKS endpoint now caches the
published key set instead of loading and parsing the keystore for every request, whichever keystore is used. The cached set
expires along with the keys CAS signs with (
cas.authn.oidc.jwks.core.jwks-cache-expiration), and never later than a day. Rotating or revoking keys, or changing a keystore file, publishes the new key set right away on that CAS node; other nodes publish it once their cached set expires. - RC3 OAuth and OpenID Connect client authentication at the token endpoint no longer builds a throw-away access token to decide which claims to release, and the authorization code or refresh token in the request is read from the ticket registry once per request instead of once by every authenticator and validator that inspects it.
- RC3 Authorization responses that return their parameters in the URL fragment, such as those of the
implicit flow, no longer repeat every query parameter of the client’s redirect URI. When CAS includes the issuer in
authorization responses (RFC 9207), it now sends it once, with the other response parameters: it used to be sent twice with
the
queryresponse mode, and also added to the query offragmentandform_postresponses. - RC3 JWT access tokens returned from the OpenID Connect authorization endpoint, with the
tokenandid_token tokenresponse types, now name the OpenID Connect issuer, as the ID token and the token endpoint do. They used to name the CAS server, so resource servers that check the issuer of a JWT access token rejected them. - RC3 When a request to an OAuth endpoint carries
no
client_idparameter, as when a client authenticates at the token endpoint with HTTP Basic, CAS now takes the client from those credentials instead of matching the redirect URI against every OAuth application each time the authenticated profile is read. Where the redirect URI still has to be matched, that happens once per request. Clients whose redirect URI patterns overlap are no longer mistaken for one another in this case. - RC3 A request object passed with the
requestparameter is now unpacked and verified once per request instead of once for every authorization parameter read from it. - RC3 With
cas.authn.oauth.code.remove-related-access-tokensenabled, presenting an authorization code again after it was redeemed now revokes the access and refresh tokens issued from it, and the access tokens later issued from that refresh token, as RFC 6749 recommends. Previously only access tokens were looked for, by searching every ticket in the registry for each unknown or expired code, and a replay rejected during client authentication, such as with PKCE orprivate_key_jwt, revoked nothing. CAS now keeps a short-lived record of the tokens issued from each redeemed code, for as long as transient session tickets live, and an unknown code no longer triggers a search of the registry. This comes with trade-offs, which is why the setting remains off by default: each code exchange writes one extra transient session ticket to the registry; a code presented again after that record has expired revokes nothing, and the record’s lifetime follows the transient session ticket expiration settings shared with other transient session tickets; tokens issued before upgrading have no record and are not revoked, where the previous search would have found their access tokens; and a code presented again while its first redemption is still issuing tokens is rejected, but the tokens being issued at that moment are not revoked. - RC3 A client that authenticates at the token endpoint with
private_key_jwtorclient_secret_jwtand sends noclient_idparameter is now identified from its client assertion, whose issuer and subject are the client identifier, instead of by matching the redirect URI against every OAuth application. - RC3 PKCE is now verified for every
authorization code exchange, whatever the client authentication method. A client that authenticated at the token endpoint
with
private_key_jwt,client_secret_jwtor mutual TLS could previously redeem a code issued with acode_challengewithout acode_verifier, or with one that did not match. As RFC 7636 and RFC 9700 require, the code verifier is now compared exactly rather than ignoring case, and acode_verifiersent for a code that was issued without a challenge is refused withinvalid_grant. Acode_challengeandcode_challenge_methodsent inside a request object are now recorded with the authorization code as well; previously only the query parameters were read, so the code was issued without the challenge and its verifier was then refused. - RC3 When an OAuth application renews refresh
tokens, the refresh token presented is now removed before new tokens are issued for it, and only the request that removes it
is issued tokens. Concurrent requests presenting the same refresh token could previously each receive a new refresh token,
so one refresh token turned into several valid ones. A client that sends the same refresh token in parallel now receives
invalid_grantfor all but one request. The refresh token being renewed is also no longer written back to the ticket registry during the request. Tokens already issued from a refresh token presented again are not revoked. - RC3 Client secrets are now compared in constant time, and expired client secrets are no longer removed while a request is authenticated: CAS used to change the shared application definition and save it to the service registry during the request, and once every secret of an application had expired and been removed, the application was treated as having no client secret and was accepted with any secret, or none. Expired client secrets are now ignored when a request is authenticated, and removed from the application definition in the background each time CAS loads applications from the service registry. When all secrets of an application have expired, the one that expired last is kept, so the application keeps refusing requests until it is given a new secret. Updating a dynamically registered client now replaces an expired client secret with a new one instead of extending the expiration of the expired secret.
- RC3 A client that authenticates at the token endpoint with
client_secret_jwtis now verified with itsclientSecret, as OpenID Connect requires, whenever the application’stokenEndpointAuthenticationMethodisclient_secret_jwtor left blank. A raw secret in the application’sjwksis still accepted, but that fallback is deprecated for removal. A secret shorter than the signing algorithm requires (32 bytes forHS256, 64 forHS512) is skipped with a warning that names the application. - RC3 When CAS works out which application a login is for, a request object carried by the OAuth or OpenID Connect authorization request is now unpacked and verified once, instead of once for each of the client identifier, redirect URI and grant type read from it.
- RC3 Rotating or revoking keys in the OpenID Connect keystore now also clears the keys CAS signs with on that node, so the new current key is used and a revoked key stops being used right away instead of once the cached keys expire.
- RC3 Changes to an OpenID Connect keystore file are announced again once CAS notices them. The file watcher has never broadcast them, so CAS kept signing with the keys it had loaded until its key cache expired.
- RC3 Ed25519 keys presented to the OpenID Connect client JWKS registration endpoint are now verified with the JDK’s own EdDSA support. Verification previously relied on Google Tink, which CAS does not ship, so such registrations failed at runtime.
- RC3 CIBA token requests answer
authorization_pendingwhile the user has not yet approved the request,expired_tokenfor an expiredauth_req_id, andinvalid_grantfor one that is unknown or already redeemed, as CIBA Core 1.0 requires; they used to returninvalid_grantandinvalid_request. - RC3 Device code token requests answer
expired_tokenfor an expired device or user code, andinvalid_grantfor a device code that is unknown, already redeemed or issued to another client, as RFC 8628 requires; they used to returnaccess_denied, which RFC 8628 reserves for a user who denied the request. - RC3 Approving consent for an authorization request with
prompt=consentnow completes the request instead of showing the consent screen again. - RC1 Custom ID token claims can also be constructed using Apache Groovy.
SAML2
- RC3 The SAML2 identity provider no longer
matches an entity id longer than
cas.service-registry.core.maximum-service-id-lengthagainst application definitions. - RC3 The SAML2 identity provider parses its signing key once instead of for every signed response or assertion; a rotated or replaced key is picked up the first time it is used.
- RC3 Back-channel SAML2 single logout notifications sent with the HTTP-POST or
HTTP-Redirect bindings now honor
cas.slo.asynchronousthe way SOAP and CAS protocol notifications already did: by default they are sent in the background, so one slow service provider no longer holds up the logout of the others, and the logout reports them as sent without waiting for a response. The incoming logout request is also parsed once per logout instead of once per service provider. - RC3 Cached SAML2 service provider metadata is reloaded in the background once three quarters of its cache lifetime has passed, so the request that finds it nearly expired no longer waits for the metadata to be fetched, parsed and validated. The current metadata stays in use until the reload completes.
- RC3 The SAML2 identity provider builds the resolver that locates its signing credentials once instead of for every signed response or assertion.
- RC3 The SAML2 identity provider decodes and
parses the incoming
SAMLRequestorSAMLResponseonce when matching it to a service provider, instead of once for every SAML2 application definition it compares against. - RC3 The SAML2 identity provider locates and verifies the service provider behind an authentication request, and resolves its metadata, once per request instead of up to four times while validating the request, choosing the binding and building the response.
- RC3 The SAML2 identity provider now looks up a service provider’s entity in its metadata once per request: the entity found while checking that the cached metadata is still usable is handed to the request, instead of being looked up a second time.
- RC3 Templates the SAML2 identity provider loads from the classpath, such as the page that posts a SAML2 response or logout request with the HTTP-POST binding, are now parsed once and cached instead of being read and parsed for every response.
- RC3 SAML2 service provider metadata committed by hand to a Git repository, such as
sp-metadata/SP.xml, keeps the same identifier across loads and can be looked up and removed by it; documents whose names contain a hyphen are no longer skipped. - RC3 With identity provider metadata kept in Redis, service providers without metadata of their own use the identity provider’s global metadata and keys, as with the other metadata stores; SAML2 responses used to fail with “Unable to locate signing credentials”.
- RC3 With identity provider metadata kept in DynamoDb or Google Cloud Storage, service providers without metadata of their own use the identity provider’s global metadata and keys, as with Redis, JPA and MongoDb.
CAS & Other Protocols
- RC1 CAS REST APIs now return a
403status code instead of a500type of error when unauthorized application requests are identified.
Authorization
- RC3 Attribute-based access strategies no longer deny a
principal only because it carries fewer attributes than the strategy lists as rejected, or, when
requireAllAttributesisfalse, fewer than it lists as required. The documentation now spells out thatrequireAllAttributesalso governs rejected attributes: by default access is refused only when every rejected attribute is present with a matching value, so a strategy that should refuse access on any one rejected attribute must setrequireAllAttributestofalse. - RC3 The HTTP request access strategy now denies
access when a configured header is missing from the request, matches header names case-insensitively, and requires the
IP address, user agent and header patterns to match the entire value. Previously a missing header satisfied its rule and
patterns matched any part of a value, so
10.0.0.1also matched110.0.0.15. Patterns written to match only part of a value, such asChrome.+for a user agent, must now be written as.*Chrome.+. - RC3 Attribute-based access strategies, including
those used by Grouper and SCIM access strategies, now require a required or
rejected attribute value pattern to match the entire attribute value. Previously a pattern matched any part of a value, so a
required
adminalso acceptedsysadmin-disabled. Patterns written to match only part of a value must now be written as, for example,.*admin.*. - RC3 The REST access strategy no longer sends the application definition to the remote endpoint, since it may carry client secrets, keys and other credentials. The request body now carries the principal id and attributes, the requested service id and the id, name, service id and type of the application definition.
Attributes & Consent
- RC1 Releasing attributes via pattern matching accepts Groovy transformation rules.
- RC1 Attributes requested for Consent may now be localized using language bundles and a prefixed language key that is
screen.consent.attributes.attribute.[attribute-name].
Tickets & Sessions
- RC3 Ticket registries that replicate tickets through a message queue, such as Kafka, AMQP, Pulsar and Google Cloud Pub/Sub, now send one message per ticket update instead of two, and a node that applies an update received from another node no longer sends it back out. Those echoed copies cost one extra message per node for every update and could overwrite a newer version of the ticket on the node that made the change.
- RC3 When a single sign-on session ends, the JPA ticket registry removes the session’s service tickets with one statement instead of reading and deleting them one at a time. Service and OAuth tickets that were updated after they were issued, which lost the link to their session when ticket encryption was enabled, now keep it and are removed with the session.
- RC3 When a single sign-on session ends, the remaining ticket registries, such as Redis, Hazelcast, DynamoDb, Cassandra, Memcached and the default in-memory registry, remove the session’s service and proxy tickets by their identifier instead of reading and decoding each one first. With registries that cannot tell whether a ticket was still present, the number of removed tickets reported for the session may include service tickets that were already validated. Removing a ticket that is already gone from the Apache Geode and the deprecated CosmosDb registries no longer fails.
- RC3 The ticket registry cleaner now looks for expired tickets first, in batches of up to ten thousand so a large backlog is never held in memory at once, and then removes up to eight of them at a time, where it used to remove them one by one while the scan was still running. With the JPA ticket registry, the database cursor, connection and transaction used for the scan are released before any ticket is removed, and rows read by the scan are no longer all kept in memory until it ends.
- RC3 A failed attempt to validate a service or proxy ticket for a different service, or for the wrong tenant, now counts as a use of the ticket, as the CAS protocol expects. Such an attempt used to leave the ticket untouched, so it could be presented again. The ticket’s expiration policy still decides what remains: a single-use ticket, the default, is removed, so a client that retries with a corrected service URL needs a new ticket, while a ticket allowed several uses keeps the rest.
- RC3 Validating a single-use service ticket now removes it in one step, and only the request that actually removes it receives a successful response. Two requests validating the same ticket at the same moment, on the same node or on different nodes, could previously both succeed with ticket registries that store copies of tickets, such as JPA, MongoDB, Redis or Hazelcast, or with ticket encryption enabled. Each validation also costs fewer trips to the ticket registry, and a validation that cannot obtain the ticket’s lock is now rejected instead of being allowed without consuming the ticket.
- RC3 When a ticket is validated, the
service URL must match the one the ticket was issued for exactly up to its query, so an encoded character, such as an
encoded
#that hides user information in the host, no longer makes two different URLs match. The query is compared once decoded, so a parameter that the application and the validating client encode differently, such as a space sent as+or as%20, still matches. - RC3 The Memcached, DynamoDB and Google Cloud Firestore ticket registries now report a ticket that was already gone when asked to remove it. Service tickets, authorization codes, device codes and CIBA requests are redeemed by whichever request removes them, and these registries reported every removal as successful, so two requests presenting the same ticket at the same moment could both succeed. A Memcached removal that fails, such as on a timeout, is no longer counted as removed either. The Cassandra ticket registry still reports every removal as successful and keeps this limitation.
- RC3 Removing a proxy-granting ticket no longer writes back the copy of the single sign-on session it was issued from, which undid any change made to the session since, such as applications the user logged into afterwards. When a single sign-on session ends, it is no longer rewritten once for each of its proxy-granting tickets and once more right before it is removed.
- RC3 The Hazelcast ticket registry keeps each ticket
for as long as the expiration policy that applies to it, so remember-me single sign-on sessions are no longer removed after
the default session lifetime, and updating a session no longer restarts its lifetime in Hazelcast. Looking up sessions by
attribute, such as for logout requests sent by external identity providers, no longer builds a query from the attribute
values, so values that contain quotes are matched instead of failing. Listing tickets page by page, such as through the
ssoSessionsactuator endpoint, now skips and counts tickets across the whole registry rather than within each ticket map, and the registry no longer shuts down the Hazelcast instance it shares with other modules, such as authentication throttling. - RC3 The Hazelcast ticket registry counts, finds and removes the tickets of a user through its indexes in one pass, with or without Jet, instead of running two SQL statements for each ticket map or reading the whole registry, and reports the number of tickets it actually removed. Users are matched regardless of case, like the other ticket registries; tickets stored before the upgrade for user names that are not in lower case are found again once they are renewed or expire. Session and service ticket counts read the size of their maps, only the indexes the registry queries are kept, and an encrypted ticket read back after encryption is turned off is removed instead of failing. Ending a single sign-on session removes its service tickets with one call per ticket map, listing all tickets reads them in batches instead of copying each ticket map into memory at once, and the ticket maps are configured before the Hazelcast member starts rather than added to the running cluster afterwards.
- RC3 The Hazelcast ticket registry no longer runs SQL
queries, so Hazelcast Jet is no longer started by any CAS Hazelcast module and the
core.enable-jetHazelcast setting, such ascas.ticket.registry.hazelcast.core.enable-jet, is removed. Remove it from your configuration; CAS refuses to start with settings it does not recognize. Thehazelcast-sqllibrary is no longer included. - RC3 The Hazelcast ticket registry stores tickets in a new,
smaller format: entries use Hazelcast compact serialization, so looking up tickets by user, by service or by attribute reads
only those fields instead of deserializing every ticket, and authentication attributes are kept only with single sign-on
sessions rather than with every ticket. Nodes running this version cannot read tickets stored by earlier versions, so
upgrade the whole cluster at once; existing tickets and single sign-on sessions are not carried over. With encryption turned on,
the application a ticket was issued for is now stored as a digest too, like the ticket id, user and attributes, instead of
as its plain URL. Since tickets no longer
go through Hazelcast’s Java serialization,
cas.ticket.registry.hazelcast.core.enable-compressionno longer applies to them. - RC3 When Hazelcast expires a single sign-on session of the Hazelcast ticket registry, CAS now sends single logout requests, removes the tickets the session still holds and reports the session as destroyed, like the ticket registry cleaner does for other ticket registries; expired sessions used to disappear silently. Each expired session is handled once, by the node that owns it.
- RC3 The
cluster.core.replicatedandcluster.core.async-fillupHazelcast settings, such ascas.ticket.registry.hazelcast.cluster.core.replicated, are removed from every Hazelcast module: no module ever used a Hazelcast replicated map, so turning them on only left the actual map, such as that of the Hazelcast ticket registry, authentication throttling or Spring Session, without its expiration, eviction, backup settings and indexes. Remove them from your configuration; CAS refuses to start with settings it does not recognize. - RC3 The MongoDB ticket registry reads only the stored ticket when fetching
tickets, counts tickets from collection metadata instead of scanning collections, and removes the service tickets of a
ticket-granting ticket with one request per collection instead of reading each one back first. Ticket identifiers are
now stored as the document
_id, which removes the separateIDX_IDindex and makes duplicate identifiers impossible; tickets stored by earlier versions are converted at startup, so all nodes sharing the registry should be upgraded together. Only ticket-granting tickets store principal attributes and carry the attribute index, and only tickets linked to a service carry the service index, so issuing and validating a service ticket no longer writes an index entry per attribute value; indexes left on other collections by earlier versions are removed at startup. - RC3 MongoDB connection pools now default to 100 connections, keep connections for 30 minutes and idle connections for
5 minutes, and wait at most 10 seconds for a free connection instead of a minute. The server heartbeat uses the
driver’s default instead of the connection
timeout. Pool settings do not apply whenclient-uriis used; set the connection string’s own options instead. If an index CAS replaces cannot be rebuilt, the previous index is restored instead of the collection being left without it. - RC3 Redis-backed counts and key lookups, such as the ticket and service registry counts, now return their connection when done. With connection pooling enabled, every call used to keep a pooled connection until the pool ran out.
- RC3 The Redis ticket registry query for ticket ids and loading all YubiKey devices from Redis also return their connection when done.
- RC3 The in-memory cache of the Redis ticket registry no longer keeps tickets that have changed or been removed on another CAS node. A cached ticket used to stay cached for as long as it was read, so a session ended on one node could stay valid on another and could even be written back to Redis. Tickets are now kept for at most 30 seconds, a ticket read while another node announces a change to it is not kept, and changes are announced to other nodes without the ticket itself, which used to be sent unencrypted to every node on every change even with ticket encryption enabled. Nodes still on an earlier version handle the new notifications correctly. Reading all tickets, as the ticket registry cleaner does, or querying tickets no longer fills the cache with every ticket read; a ticket first seen by such a scan is read from Redis the next time it is needed, instead of being served from the cache.
- RC3 The Apache Pulsar ticket registry subscribes each CAS node under a subscription of its own, named after
cas.ticket.registry.pulsar.subscription-nameand the node’scas.ticket.registry.core.queue-identifier, so every node receives every ticket change; nodes used to share one subscription. Ticket registry commands now travel as JSON strings written by CAS, as they do with the Kafka ticket registry, rather than through Pulsar’s JSON schema, which could not carry them. - RC3 The Google Cloud Pub/Sub ticket registry subscribes each CAS node under a subscription of its own, named
CasTicketRegistryTopicSubscription-followed by the node’scas.ticket.registry.core.queue-identifier, so every node receives every ticket change; nodes used to share one subscription. - RC3 A login from a client that sends no
User-Agentheader no longer fails with a server error when the ticket-granting cookie is pinned to the client session; the cookie is pinned to the absence of a user agent instead. - RC1 RediSearch functionality now supports Redis clustering.
- RC1 The maximum lifetime of a transient session ticket (i.e.
TST) is by default reduced from15minutes to5minutes. - RC1 LettuceMod is removed from CAS and its functionality is directly provided by Lettuce itself.
Service Registry
- RC3 When the background reload is enabled, a request for an application that matches no cached service definition no longer
reads the whole service registry; definitions added outside CAS, for example by another node, are recognized at the
next scheduled reload. Changes picked up by the JSON and
YAML registry watchers now update the cache right away, fetching services
by type through the
registeredServicesactuator endpoint no longer empties the cache, and CAS warns when the cache expires definitions before the scheduler reloads them. See this guide. - RC3 Exporting a single service definition through the
registeredServicesactuator endpoint now answers404for an unknown identifier instead of failing with an error. - RC3 Saving or deleting a service definition while the service registry is being reloaded now waits for the reload
to finish. Previously, a save or delete that waited more than five seconds was dropped without an error; the
registeredServicesactuator endpoint reported success and a dynamically registered client could receive credentials that were never stored. - RC3 A service registry that fails to load, such as a REST service registry whose endpoint is unavailable or answers with an error, no longer empties the cache of service definitions; CAS logs the failure and keeps the definitions it already loaded until a later reload succeeds.
- RC3 Groovy scripts referenced by service definitions as files, for access strategies, their activation criteria, attribute release activation criteria, single sign-on participation policies, attribute filters and surrogate access strategies, are now compiled and watched once per script location instead of once per service definition on every reload or evaluation. Previously each reload leaked a file watcher, which could exhaust the operating system’s limit on file watchers.
- RC3 Expired application definitions are processed once each time service definitions are loaded instead of on every request. Contacts of an expired application are notified once per expiration date rather than on every request and every reload, a request for an expired application no longer sends notifications, and definitions set to be deleted when they expire are no longer found by requests and are deleted at the next load instead of on the request thread.
- RC3 Lookups in the JSON and YAML service registries no longer read a map that a reload, a save or the file watcher is modifying at the same time.
- RC3 Service registry listeners that replace a loaded application definition now take effect; the REST service registry, among others, used to return the definitions as they were before the listeners ran. Single sign-on checks no longer fail with an error when no service is given.
- RC3 Inline Groovy scripts used by access strategy and attribute release activation criteria and by single sign-on participation policies are compiled once and shared, instead of being compiled again on every evaluation. Groovy script files used by service definitions resume picking up changes after the script cache expires them, and compiling one script for the first time no longer holds up compiling a different one.
- RC3 A REST service registry that fails to load no longer reports credentials or query parameters of its endpoint URL in the error.
- RC3 Application definitions that CAS reads straight from the service registry, because they are not cached yet, now get their templates applied, like definitions that are loaded in bulk. Previously settings that a template supplies, such as access or multifactor authentication rules, were missing from such definitions until the next reload.
- RC3 Values that only resemble a whole number, such as
1.5,1e3,0x1For one too large to store, no longer fail the request with a server error. Aserviceparameter, including the one used to apply an application’s HTTP security headers and to fetch SAML2 identity provider metadata, is matched as a service URL instead of an identifier; the issue instant of an IdP-initiated SAML2 request, the WS-Federationwfreshparameter, the single sign-on revocation attribute, numeric durations, cookie lifetimes and the actuator endpoints that accept a service identifier handle such values the same way. Cookie lifetimes too large for a cookie are capped instead of overflowing. - RC3 When the service registry is consulted for an application that matches no cached definition, the JPA and the deprecated CosmosDb service registries now evaluate every definition the way other registries do. Both used to search for definitions whose pattern contained the requested URL, which never found definitions written as regular expressions; CosmosDb also placed the requested URL in its query text unescaped and no longer sends it to the database at all.
- RC3 Service lookups against the Git service registry use the definitions from the last load instead of pulling from the remote repository on each lookup; definitions are pulled when service definitions are reloaded.
- RC3 Application definitions that are saved through CAS, picked up by the JSON and YAML registry watchers, or announced by Google Cloud Storage change notifications now have their templates applied before they are cached, as they are when definitions are loaded, instead of being matched without them until the next reload.
- RC3 The MongoDB service registry deletes a service definition in one request instead of reading it back first, counts definitions from collection metadata instead of scanning the collection, and, when several definitions share a service id or name, returns the one that comes first in evaluation order instead of an arbitrary match. Definitions it looks up by id or name, or reads while the cache is empty, now get the same post-processing as those it loads in bulk, so OpenID Connect applications keep their scope-based attribute release policies.
- RC3 When MongoDB runs as a replica set, the MongoDB service registry watches its collection through a change stream and reloads service definitions on every CAS node shortly after they change, including changes made by another node or directly in MongoDB. Deployments that are not replica sets keep relying on the scheduled reload.
- RC3 The Amazon S3 service registry names buckets after service definitions with only the characters S3 accepts, so definitions whose names carry spaces or other punctuation can be saved; it saves definitions that have no description, and renaming a definition no longer leaves a copy behind in its old bucket.
Interface & Accounts
- RC3 The account profile and the account management screens (sign-up, password reset, forgot username, expired or must-change password) are redesigned: the profile gets a section rail, an overview with summary cards and lists with inline details, filtering and keyboard support, and every account management flow shows its steps beside a single form card. Features are unchanged; colors follow the CAS theme.
- RC3 Buttons and text fields on CAS pages share one look: labels sit above rounded inputs, buttons are pill-shaped and password fields get round reveal and generate buttons, matching the account screens and following the CAS theme colors. Alert boxes, tables and progress bars follow the same style, and page layouts stay as they were.
- RC3 WebAuthn and consent screens are redesigned. WebAuthn login, registration and QR code pages sit in a centered card with a status icon, and registration offers passkeys and security keys side by side. OpenID Connect, OAuth and CIBA consent show the application up front, list each scope and claim with its own icon, and show the CIBA binding message as a large code. Consent no longer shows two Deny buttons, CIBA lists scope names again, OAuth scopes without a description no longer show a raw message key, and the WebAuthn status heading now updates during login and registration.
- RC3 Interrupt and attribute consent screens follow the same style: a centered card with a status icon, the interrupt message as a callout with its links, attribute consent tabs as a segmented control and consent options as selectable cards. Tabs no longer show a focus ring when a page loads.
- RC3 The rest of the CAS screens follow the same style: error, account status, logout and confirmation pages show a status icon in a centered card, multifactor, passwordless, delegated, surrogate and other sign-in steps show the provider’s icon above their form, provider and option choices appear as cards, and the login card picks up the same surface.
- RC3 Palantir dialogs, including the service wizard and editor, use the same look as the rest of CAS: rounded dialogs with a plain title bar, matching buttons (the main action highlighted), select menus and multi-value fields that look like the other inputs, round row buttons, and restyled menus, tooltips and confirmation prompts. Dialogs no longer fail to position themselves when opened before any right-click menu was set up.
- RC3 Palantir has a navigation bar with grouped menus (Identity, Sessions, Platform) in place of the side rail, and each page is headed by the section it shows. Applications can be shown as cards or as the table, and a card’s menu offers the same actions as right-clicking a row. Palantir follows the light or dark appearance of the operating system until another is chosen from the navigation bar or the settings dialog. The service wizard starts from a choice of described application types and then walks the definition in steps (Basics, Protocol, Access & attributes, Policies) to a review of what the definition holds: each step shows its sections as tiles that say whether they hold values, with Back and Next to move along. Choosing the Policies step, whose sections are all advanced options, shows the advanced options. The Feature Catalog lists each feature as a card with its modules, with a search over feature and module names.
- RC3 Palantir shows its SAML2 Metadata Sources tab whenever a SAML2 service provider is registered; the tab stayed hidden, or appeared only when the last definition read was a SAML2 one.
- RC3 Palantir service wizard dropdowns react to a choice again: picking a username attribute provider, attribute release policy or another type now shows that type’s fields and adds it to the definition.
- RC3 Pressing Enter in a Palantir search or test form, such as surrogate accounts or trusted multifactor devices, runs the form’s main action; some forms used to ignore it and others reloaded the dashboard.
- RC3 A login form submitted with a malformed
executionvalue now restarts the login flow instead of failing with a server error.
Platform & Operations
- RC3 Groovy scripts, whether inline or in a file, that keep no state between
executions no longer run one request at a time: each execution gets its own instance of the compiled script, so concurrent
requests through the same script run in parallel. Scripts that declare fields other than
finalones holding plain values, such as counters or caches, still run one execution at a time so that their state stays consistent. - RC2 CAS actuator endpoints that are built on Spring MVC request mappings now honor
management.endpoint.<id>.access. AREAD_ONLYdeclaration registers only the endpoint’s read mappings, so itsPOST,PUT,PATCHandDELETEmappings are no longer reachable, and aNONEdeclaration registers no mappings at all. - RC2 CloudWatch logging now avoids recursive logging initialization when reporting appender startup or delivery failures.
- RC2 Several optimizations are in place to assist with faster startup time, allowing for more components to be lazily initialized.
Build & Project
- RC1 RC2 RC3 RC4 A large number of dependencies and libraries have been updated to their latest versions.
- RC3 The unit, integration and functional test workflows, like the native image tests, can be started manually to run only the test categories or browser scenarios that failed in the previous run, against the latest changes. See this guide.
- RC3 Almost all CAS unit tests are internally reworked to allow maximum parallelization and speed up the overall test execution time.
- RC3 MongoDb integration tests have now switched to using MongoDb
9.x. - RC3 The project README is reworked as a landing page, with a one-minute Docker try-out, a link to the new Quick Start, features grouped by area and the maintained documentation versions.
- RC2 Authentication history, theme caching and CloudWatch shutdown now use concurrent collections and explicit coordination in place of Java monitor locking.
- RC1 Groovy integration tests have now switched to use Groovy
5.1.x. - RC1 Redis integration tests have now switched to use Redis
8.10.x.
General
- RC3 CAS no longer tries to match a service identifier, such as the
serviceparameter, that is longer than 4096 characters against application definitions, which guards against expensive regular expression matching on oversized input. The limit is controlled bycas.service-registry.core.maximum-service-id-length; zero or a negative value disables it. - RC3 New application definitions without an identifier receive a random positive identifier instead of one derived from the system clock, so definitions created at the same time, on the same or different nodes, no longer collide.
- RC3 Checking access to an application now looks up the additional access strategy enforcers once instead of on every request.
- RC3 Queries against the service index return their results in full instead of reading from a result set that is already closed.
- RC3 Looking up an application definition by name or by identifier and type no longer examines every cached definition. A lookup by identifier for a different type of definition now finds nothing instead of returning the definition of the other type.
- RC1 A number of date-formatting operations have switched their base timezone from system default to
UTC.