OpenID Connect (OIDC) is an identity layer built on top of the OAuth 2.0 protocol, designed to enable secure authentication and authorization in the context of web and mobile applications. It provides a standardized way for users to authenticate themselves and for applications to verify the identity of users without needing to handle sensitive credentials like passwords directly.
Apereo CAS has extended its capabilities to support various authentication protocols including OAuth 2.0 and OpenID Connect. In this post, we will briefly focus on the steps required to turn on OIDC support for Apereo CAS. This tutorial specifically focuses on:
7.0.x
21
The typical setup is quite simple once you include the relevant extension module in your build. Once you have a functioning build, you’ll need to define two key settings in your CAS configuration file, i.e. cas.properties
:
cas.authn.oidc.core.issuer=https://sso.example.org/cas/oidc
cas.authn.oidc.jwks.file-system.jwks-file=/path/to/keystore.jwks
The issuer is essentially you; the entity responsible for issuing identity tokens and providing authentication services to users and client applications. The issuer URL serves as a unique identifier for the OpenID Connect Provider and is typically the base URL where the provider’s configuration information can be retrieved. This URL is used by client applications to discover and interact with the OpenID Provider dynamically.
A JWKS or JSON Web Keystore is used to publish the cryptographic keys that are used to sign identity tokens issued by the OP. CAS will use this JWKS to publish the public keys used to verify the authenticity of identity tokens. When a client application receives an identity token from CAS, it can retrieve the corresponding public key from the JWKS and use it to verify the token’s signature. This ensures that the token was indeed issued by CAS and hasn’t been tampered with. Note that if the keystore file is not found, it will be automatically generated by CAS.
A typical JWKS file looks like this:
{
"keys": [
{
"kty": "RSA",
"kid": "cas-YQQAQRBg",
"use": "sig",
"n": "s...",
"e": "AQAB",
"d": "...",
"p": "...",
"q": "...",
"dp": "...",
"dq": "...",
"qi": "...",
"state": 0
}
]
}
Now you can begin registering your client applications with CAS whose registration record may likely be managed in flat JSON files:
{
"@class": "org.apereo.cas.services.OidcRegisteredService",
"clientId": "client",
"clientSecret": "secret",
"serviceId": "^http://client.example.org/oidc",
"name": "Sample",
"id": 1,
"bypassApprovalPrompt": true,
"description": "My client application",
"scopes" : [ "java.util.HashSet", [ "openid", "profile" ] ],
"supportedGrantTypes": [ "java.util.HashSet", [ "authorization_code" ] ],
"supportedResponseTypes": [ "java.util.HashSet", [ "code" ] ]
}
In OpenID Connect (OIDC), scopes are strings that define the specific access rights and information that a client application requests from CAS on behalf of the authenticated user. Scopes essentially define the level of access that the client application is requesting to the user’s information or resources.
OIDC specification defines several standard scopes that represent common types of access requests, including:
openid
: This scope is required for OIDC authentication and indicates that the client application is requesting authentication services from CAS.profile
: The profile scope requests access to the user’s profile information, such as their name
and other basic profile attributes.email
: This scope requests access to the user’s email address.address
: The address scope requests access to the user’s physical address information.phone
: This scope requests access to the user’s phone number.In addition to these standard scopes, CAS also allows for custom scopes. Custom scopes enable more granular control over the types of access requests that can be made by client applications.
In OpenID Connect (OIDC), claims are pieces of information about an authenticated user that are asserted by CAS and included in the identity token (i.e. id_token
) or userinfo
response. Claims provide structured data about the user, such as their identity attributes, profile information, or custom data.
Claims can include a wide range of information, including:
Claims are typically represented as key-value pairs, where the keys represent the names of the claims and the values represent the corresponding user data. For example, a claim might be “email” with the value “user@example.com”, indicating the user’s email address.
Standard claims that are mapped to a standard scope such as profile
can only be released if the claim is already found and resolved by CAS as an attribute. In other words, to release the family_name
claim as part of the profile
scope, CAS must be configured to know how to fetch that exact data piece and attribute from a source. A static source might look like the following:
cas.authn.attribute-repository.stub.attributes.family_name=Apereo
The Authorization Code Flow is one of the widely used and most secure OAuth 2.0 grant types for obtaining an access token to access protected resources on behalf of a user. This flow is designed to keep the user’s credentials secure and is commonly used in web applications. Here’s how the Authorization Code Flow works:
https://sso.example.org:8443/cas/oidc/oidcAuthorize \
?response_type=code&redirect_uri=http://client.example.org/oidc \
&client_id=client&scope=openid%20profile
User Authorization: The user is prompted to log in and authorize the client application to access their protected resources. The user may need to enter their credentials on the CAS login page. After successful authentication, CAS asks the user to grant or deny the client’s request. In our setup, this approval is bypassed and skipped via the bypassApprovalPrompt
flag.
Authorization Code: If the user grants permission, CAS generates an authorization code and sends it to the client application’s specified redirect URI. This code is short-lived and is not the actual access token:
302 https://client.example.org?code=OC-1234567890
POST https://sso.example.org/cas/oidc/token \
?client_id=client&client_secret=secret \
&redirect_uri=https://client.example.org \
&grant_type=authorization_code&code=OC-1234567890
{
"access_token": "AT-1-....",
"token_type": "Bearer",
"expires_in": 28800,
"scope": "openid profile",
"id_token": "...."
}
In OpenID Connect (OIDC), an ID token is ALWAYS a JSON Web Token (JWT) that is issued by CAS and contains claims (pieces of information) about the authenticated user and is typically used by client applications to verify the user’s identity and obtain basic profile information.
Signature: The ID token is digitally signed by CAS to ensure its authenticity and integrity. Client applications can verify the signature using the CAS public keys, which are typically obtained from the JWKS endpoint.
Expiration: Like other JWTs, the ID token includes an expiration timestamp that indicates when the token expires and should no longer be considered valid.
Validating an ID token involves retrieving the CAS public keys from the JWKS endpoint:
GET https://sso.example.org/cas/oidc/jwks
A typical ID token issued by CAS would be similar to the following:
{
"jti": "TGT-1-...",
"sid": "eb4a7523e5e5cd49a6e772f9c472a43459e510ee",
"iss": "https://sso.example.org/cas/oidc",
"aud": "client",
"exp": 1715790223,
"iat": 1715761423,
"nbf": 1715761123,
"sub": "casuser",
"amr": [
"Static Credentials"
],
"client_id": "client",
"auth_time": 1715761421,
"nonce": "3d3a7457f9ad3",
"at_hash": "_dyesp9bV-cG3C1iZTYPjw",
"family_name": "Apereo",
"name": "apereo-cas",
"preferred_username": "casuser",
"txn": "..."
}
If you have questions about the contents and the topic of this blog post, or if you need additional guidance and support, feel free to send us a note and ask about consulting and support services.
I hope this review was of some help to you and I am sure that both this post as well as the functionality it attempts to explain can be improved in any number of ways. Please feel free to engage and contribute as best as you can.
Happy Coding,
Monday-Friday
9am-6pm, Central European Time
7am-1pm, U.S. Eastern Time
Monday-Friday
9am-6pm, Central European Time