Search code examples
cookiessingle-sign-onkeycloakkeycloak-servicestoken-exchange

Establish SSO/set cookies with access or id token/token exchange


I'm allowing users logged in an external application to jump into our application with their access token through Keycloak's identity brokering and external to internal token exchange.

Now I'd like to establish an SSO session in an embedded JxBrowser in our application similar to a regular browser login flow, where three cookies are set in the browser: AUTH_SESSION, KEYCLOAK_SESSION(_LEGACY) and KEYCLOAK_IDENTITY(_LEGACY).

KEYCLOAK_IDENTITY contains a token of type Serialized-ID which looks somewhat similar to an ID token.

Is it possible to create the KEYCLOAK_IDENTITY cookie using the exchanged (internal) access and/or ID token and, provided that the other two cookies are correctly created as well, would this establish a valid SSO session?

Basically all I am missing is how I could obtain or create the Serialized-ID type token.


Solution

  • One way to achieve this:

    1. Implement a custom endpoint following this example

    Note that the provider works fine for me without registering it in standalone.xml, I'm just adding the JAR to the Keycloak Docker image.

    1. Add a method that validates a given access token, looks up the user, gets the user session and sets the cookies in the response (most error handling omitted for brevity):

      @GET
      @Produces(MediaType.APPLICATION_JSON)
      @Path("sso")
      public Response sso(@Context final HttpRequest request) {
          final HttpHeaders headers = request.getHttpHeaders();
          final String authorization = headers.getHeaderString(HttpHeaders.AUTHORIZATION);
          final String[] value = authorization.split(" ");
          final String accessToken = value[1];
          final AccessToken token = Tokens.getAccessToken(accessToken, keycloakSession);
      
          if (token == null) {
              throw new ErrorResponseException(Errors.INVALID_TOKEN, "Invalid access token", Status.UNAUTHORIZED);
          }
      
          final RealmModel realm = keycloakSession.getContext().getRealm();
          final UriInfo uriInfo = keycloakSession.getContext().getUri();
          final ClientConnection clientConnection = keycloakSession.getContext().getConnection();
      
          final UserModel user = keycloakSession.users().getUserById(token.getSubject(), realm);
      
          final UserSessionModel userSession = keycloakSession.sessions().getUserSession(realm, token.getSessionState());
      
          AuthenticationManager.createLoginCookie(keycloakSession, realm, user, userSession, uriInfo, clientConnection);
      
          return Response.noContent().build();
      }
      

    Disclaimer: I am not completely certain this implementation does not imply any security issues, but since Tokens.getAccessToken(accessToken, keycloakSession) does full validation of the access token, setting the cookies should only be possible with a valid access token.

    For CORS, add:

    @OPTIONS
    @Produces(MediaType.APPLICATION_JSON)
    @Path("sso")
    public Response preflight(@Context final HttpRequest request) {
        return Cors.add(request, Response.ok("", MediaType.APPLICATION_JSON))
                .auth()
                .preflight()
                .allowedMethods("GET", "OPTIONS")
                .build();
    }
    

    and in sso():

        return Cors.add(request, Response.ok("", MediaType.APPLICATION_JSON))
                .auth()
                .allowedMethods("GET")
                .allowedOrigins(token)
                .build();
    

    What I am uncertain about is why Firefox preflights the GET request, making it necessary to handle that.