Apereo CAS - Are We Logged In Yet?

Posted by Misagh Moayyed on June 14, 2019 · 7 mins read ·
Content Unavailable
Your browser is blocking content on this website. Please check your browser settings and try again.
This blog post was originally posted on Apereo GitHub Blog.

Overview

As users navigate back and forth between applications that are integrated with CAS, SSO sessions are established for each browser session where a special cookie is exchanged with the browser to maintain a link between the user SSO session and the underlying CAS server managing that state typically via its ticket registry. This special cookie typically is restricted to the CAS server only and is also signed and encrypted to protect replay attacks, etc.

Of course, users may log out of CAS removing the SSO session and the cookie, or the SSO session might timeout on its own thus invalidating the cookie state. In either scenario, a valid question might be:

How could an application determine whether an SSO session tied to the user’s browser is still valid and accepted by CAS?

A more traditional approach would be to try to take advantage of the gateway feature of the CAS protocol:

If this parameter is set, CAS will not ask the client for credentials. If the client has a pre-existing single sign-on session with CAS, or if a single sign-on session can be established through non-interactive means (i.e. trust authentication), CAS MAY redirect the client to the URL specified by the service parameter, appending a valid service ticket…If the client does not have a single sign-on session with CAS, and a non-interactive authentication cannot be established, CAS MUST redirect the client to the URL specified by the service parameter with no “ticket” parameter appended to the URL.

The basic premise is receiving a ticket back from CAS indicates a valid SSO session and its absence indicates otherwise. In this scenario, CAS does attempt to validate and verify the SSO session tied to the CAS cookie to determine whether or not a ticket should be issued.

While this works for certain scenarios, it is quite chatty and does involve quite of bit of back and forth. As an alternative, another approach would be to build a special endpoint inside CAS that would be more REST friendly to check on the status of SSO without involving the browser as much with ‍302 redirects and without the implicit assumption of the CAS protocol as the mediator. Note that one caveat with this new approach would be that the caller, our application, would need to have access to the CAS special cookie to pass it onto our endpoint for follow-up processing and reporting on the SSO session status.

Existing Functionality
Note that an sso endpoint does exist in CAS already that is modeled as a Spring Boot Actuator endpoint which more or less delivers this functionality. Our use case here is slightly more custom, thus the need for a new special endpoint that is in concept similar to the sso endpoint.

Let’s get started with a prototype. Our starting position is based on:

Configuration

Let’s extend our CAS configuration to include a special endpoint to report back on SSO status:

@Configuration("SomeConfiguration")
public class SomeConfiguration {
    @Autowired
    @Qualifier("defaultTicketRegistrySupport")
    private TicketRegistrySupport ticketRegistrySupport;

    @Autowired
    @Qualifier("cookieValueManager")
    private CookieValueManager cookieValueManager;

    @Bean
    public IsLoggedInController isLoggedInController() {
        return new IsLoggedInController(cookieValueManager,
            ticketRegistrySupport);
    }
}

Our humble endpoint, simply named as isloggedin, could be something as follows:

@RequiredArgsConstructor
@RestController("isLoggedInController")
public class IsLoggedInController {
    private final CookieValueManager cookieValueManager;
    private final TicketRegistrySupport ticketRegistrySupport;

    @GetMapping(path = {"/isloggedin"},
                produces = MediaType.APPLICATION_JSON_VALUE)
    public Map isLoggedIn(@RequestParam("tgc")
                          String cookieValue) {
        try {
            String tgtId = cookieValueManager.obtainCookieValue(cookieValue, request);
            Authentication auth = ticketRegistrySupport.getAuthenticationFrom(tgtId);
            if (auth != null) {
                Principal principal = auth.getPrincipal();
                Map attributes = principal.getAttributes();
                Map results = new HashMap();
                /*
                Populate the results with values
                from the principal and/or attributes...
                */
                return results;
            }
            return new HashMap<>();
        catch (Exception e) {
            LOGGER.error(e.getMessage(), e);
            return new HashMap<>();
        }
    }
}

The response type returned is set to application/json and the response status code is 200.

We should also turn off cookie session pinning:

cas.tgc.pinToSession=false

Finally, to invoke the script a client application would invoke the equivalent of the following request:

curl https://sso.example.org/cas/isloggedin?tgc=[ticket-granting cookie value]

Remember that the caller should be able to read the CAS cookie. Its only job is to pass it onto CAS, as the cookie content is entirely meaningless and the CAS server is the only authority who can decrypt and parse its contents.

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 know that all other use cases, scenarios, features, and theories certainly are possible as well. Feel free to engage and contribute as best as you can.

Happy Coding,

Misagh Moayyed