Apereo CAS - OpenID Connect Identity Provider

Posted by Misagh Moayyed on May 16, 2024 · 16 mins read ·
Content Unavailable
Your browser is blocking content on this website. Please check your browser settings and try again.

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:

  • CAS 7.0.x
  • Java 21

Setup

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
    }
  ]
}

Applications

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" ] ]
}

Scopes

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.

Claims

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:

  • Standard Claims: These are predefined claims that represent common user attributes, such as name, email address, profile picture URL, and so on. OIDC defines a set of standard claims that OPs like CAS typically include in identity tokens or provide via the userinfo endpoint.
  • Custom Claims: OPs can also define custom claims to represent additional user information that is specific to their implementation or use case. These custom claims can provide domain-specific data or extend the standard claims with additional attributes.

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

Authorization Code Flow

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:

  • User Initiates the Flow: The process begins when a user tries to access a protected resource on a client application (e.g., a web or mobile app). The client application redirects the user to CAS with specific parameters, including the requested scope, client ID, and a redirect URI:
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
  • Client Requests Access Token: The client application, now in possession of the authorization code, sends a confidential request to CAS, including the authorization code and client secret. The client also specifies the same redirect URI used in the initial request:
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
  • Authorization Server Validates and Responds: CAS validates the request. If everything checks out (valid code, matching redirect URI, and correct client secret), it responds with an access token:
{
  "access_token": "AT-1-....",
  "token_type": "Bearer",
  "expires_in": 28800,
  "scope": "openid profile",
  "id_token": "...."
}
  • Client Accesses Protected Resource: The client can use the access token to make requests to CAS on behalf of the user. The access token provides the necessary permissions to access the user’s data. Alternatively, the client may choose to validate and parse the ID token.

ID Tokens

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.

  • User Information: The ID token contains claims about the authenticated user, such as their unique identifier (sub), name, email address, profile picture URL, and other profile attributes. These claims provide basic information about the user’s identity.
  • 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": "..."
}

Need Help?

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.

So…

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,

Misagh Moayyed