Skip to content
Advertisement

create keycloak endpoint to provide custom authentication

I am trying to extend keycloak by creating a new endpoint to authenticate users.

The point is, user is not stored in keycloak, the user is stored in an external system.

The external system will call the new endpoint and provide token (will contains user info), clientId, and clientSecret. and (somehow) we will verify the existence of the user.

The challenge I am facing right now is I cannot create a session for the user. (seems the session requires existed user in keycloak, I am using InMemoryUser)

package com.mhewedy.auth;

import org.jboss.logging.Logger;
import org.jboss.resteasy.spi.HttpRequest;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.keycloak.Config;
import org.keycloak.events.EventBuilder;
import org.keycloak.models.*;
import org.keycloak.protocol.AuthorizationEndpointBase;
import org.keycloak.protocol.oidc.TokenManager;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.services.resource.RealmResourceProvider;
import org.keycloak.services.resource.RealmResourceProviderFactory;
import org.keycloak.services.resources.Cors;
import org.keycloak.services.util.DefaultClientSessionContext;
import org.keycloak.storage.adapter.InMemoryUserAdapter;

import javax.ws.rs.*;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.UUID;

import static org.keycloak.services.resources.Cors.ACCESS_CONTROL_ALLOW_ORIGIN;
import static org.keycloak.utils.MediaType.APPLICATION_JSON_TYPE;

public class MyEndpoint extends AuthorizationEndpointBase implements RealmResourceProvider {

    private final Logger logger = Logger.getLogger(MyEndpoint.class);


    private final TokenManager tokenManager = new TokenManager();

    public MyEndpoint(RealmModel realm, EventBuilder event) {
        super(realm, event);
    }

    @Override
    public Object getResource() {
        return this;
    }

    @Override
    public void close() {

    }

    @GET
    @Path("authenticate")
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
    public Response authenticate(@FormParam("token") String token,
                                 @FormParam("client_id") String clientId,
                                 @FormParam("client_secret") String clientSecret,
                                 @Context HttpRequest request) {

        // validate client_id & client_secret
        // validate token

        logger.info("generating access token...");


        String userId = UUID.randomUUID().toString();
        UserModel userModel =
                new InMemoryUserAdapter(session, session.getContext().getRealm(), userId);
        userModel.setUsername(token);
        userModel.setEnabled(true);


        // this session object doesn't contain the userModel, cause it seems it lookups the user by id and doesn't find it
        UserSessionModel userSession = session.sessions().createOfflineUserSession(session.sessions().createUserSession(
                session.getContext().getRealm(),
                userModel,
                token,
                "192.168.1.1",
                "My",
                false,
                null,
                null
        ));

        ClientModel clientModel = realm.getClientByClientId(clientId);

        logger.infof("Configurable token requested for username=%s and client=%s on realm=%s",
                userModel.getUsername(), clientModel.getClientId(), realm.getName());

        AuthenticatedClientSessionModel clientSession = session.sessions().createClientSession(realm, clientModel, userSession);

        ClientSessionContext clientSessionContext =
                DefaultClientSessionContext.fromClientSessionScopeParameter(clientSession, session);

        AccessToken newToken = tokenManager
                .createClientAccessToken(session, realm, clientModel, userModel, userSession, clientSessionContext);

        newToken.expiration(10 * 10 * 10 * 10);

        EventBuilder eventBuilder = new EventBuilder(realm, session, session.getContext().getConnection());
        AccessTokenResponse response = tokenManager
                .responseBuilder(realm, clientModel, eventBuilder, session, userSession, clientSessionContext)
                .accessToken(newToken)
                .build();

        return buildCorsResponse(request, response);
    }

    private Response buildCorsResponse(@Context HttpRequest request, AccessTokenResponse response) {
        Cors cors = Cors.add(request)
                .auth()
                .allowedMethods("POST")
                .auth()
                .exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS, ACCESS_CONTROL_ALLOW_ORIGIN)
                .allowAllOrigins();
        return cors.builder(Response.ok(response).type(APPLICATION_JSON_TYPE)).build();
    }

    // ----------------------------------------------------------------------------------------------------------------

    public static class MyEndpointFactory implements RealmResourceProviderFactory {

        @Override
        public RealmResourceProvider create(KeycloakSession session) {
            KeycloakContext context = session.getContext();
            RealmModel realm = context.getRealm();
            EventBuilder event = new EventBuilder(realm, session, context.getConnection());
            MyEndpoint provider = new MyEndpoint(realm, event);
            ResteasyProviderFactory.getInstance().injectProperties(provider);
            return provider;
        }

        @Override
        public void init(Config.Scope config) {

        }

        @Override
        public void postInit(KeycloakSessionFactory factory) {

        }

        @Override
        public void close() {

        }

        @Override
        public String getId() {
            return "MyEndpoint";
        }
    }
}

I am using code from here but the use case is differnt.

Advertisement

Answer

I solved by saving the user in the cache (db) if not exist:

String username = getUsernameFromToken(token);
String userId = "my-" + username;
UserModel userModel = new InMemoryUserAdapter(session, session.getContext().getRealm(), username);
userModel.setUsername(username);
userModel.setEnabled(true);

if (session.users().getUserById(realm, userId) == null) {
    session.userCache().addUser(realm, userId, username, false, false);
}
User contributions licensed under: CC BY-SA
8 People found this is helpful
Advertisement