BadRequestError at ResponseContext.callback when using express-openid-connect

I have been trying to integrate Keycloak SSO as authentication provider for an application I am building. I am using openid-connect provider from keycloak. I have a frontend, an express server and a keycloack service all running as containers behing a caddy reverse HTTPS proxy container. The custom express server helps me check if a user is whitelisted/admin based on the role assigned to them. I have been facing the below issue for some time now.

BadRequestError
    at ResponseContext.callback (/app/node_modules/express-openid-connect/lib/context.js:396:15)

Below given is the prototype code which I have used to connect keycloak with my custom express server. Once a user has logged in, I am passing them a custom JWT token for session state management

const express = require('express');
const cors = require('cors');                                                                                                                   
const cookieParser = require('cookie-parser');
const { auth } = require('express-openid-connect'); 
const jwt = require('jsonwebtoken'); 
const authCustomRoutes = require('./routes/routes'); 

const PORT = process.env.EXPRESS_PORT || 3001;
const CLIENT_URL = process.env.REACT_APP_BASE_URL || 'http://localhost:3000';
const CUSTOM_JWT_SECRET = process.env.JWT_SECRET;
const CUSTOM_TOKEN_COOKIE = 'access_token'; 

const app = express();
app.set('trust proxy', 1); 
app.use(cors({
    origin: CLIENT_URL,
    credentials: true,
}));

app.use(cookieParser());
app.use(express.json());

const config = {
    authRequired: false,
    auth0Logout: true,
    secret: process.env.SESSION_SECRET,
    baseURL: process.env.VITE_AUTH_SERVICE_BASE_URL,
    clientID: process.env.KEYCLOAK_CLIENT_ID,
    issuerBaseURL: process.env.KEYCLOAK_ISSUER_BASE_URL,
    clientSecret: process.env.KEYCLOAK_CLIENT_SECRET,
    routes: {},
    authorizationParams: {
        response_type: 'code',
        response_mode: 'query', 
        scope: 'openid profile email roles', 
    },
    session: {
        name: 'appSession',
        cookie: {
            httpOnly: true,
            sameSite: 'Lax'
        }
    },
    afterCallback: (req, res, session) => {
        try{
            console.log('OIDC afterCallback triggered.');
            const claims = session.id_token_claims;
            const { email, name } = claims;
            let role = 'APP_USER';
            if (
                claims.resource_access &&
                claims.resource_access[process.env.KEYCLOAK_CLIENT_ID] &&
                claims.resource_access[process.env.KEYCLOAK_CLIENT_ID].roles &&  claims.resource_access[process.env.KEYCLOAK_CLIENT_ID].roles.includes('APP_ADMIN')
            ) {
                role = 'APP_ADMIN';
            }
            const isWhitelisted = (role === 'APP_ADMIN' || (claims.resource_access &&
                claims.resource_access[process.env.KEYCLOAK_CLIENT_ID] &&
                claims.resource_access[process.env.KEYCLOAK_CLIENT_ID].roles &&     claims.resource_access[process.env.KEYCLOAK_CLIENT_ID].roles.includes('APP_USER')));
    
            if (!isWhitelisted) {
                console.warn(`Unauthorized login attempt via OIDC: User ${email} does not have APP_USER or APP_ADMIN role.`);
                res.clearCookie('appSession');
                res.redirect(`${CLIENT_URL}/login-error?error=unauthorized`);
                throw new Error('User not whitelisted based on Keycloak roles.');
            }
    
            const customJWTPayload = { email, name, role };
            const customToken = jwt.sign(customJWTPayload, CUSTOM_JWT_SECRET, { expiresIn: '1h' });
    
            res.cookie(CUSTOM_TOKEN_COOKIE, customToken, {
                httpOnly: true,
                secure: process.env.NODE_ENV === 'production',
                sameSite: 'Lax',
                maxAge: 3600000
            });
    
            console.log(`User ${email} logged in successfully via Keycloak with role ${role}. Custom JWT issued.`);
            return session;
        } catch(error) {
            console.log("Error in callback function : ", error);
            throw error;
        }
    }
};

app.use(auth(config));
app.use('/auth', authCustomRoutes);
app.get('/', (req, res) => {
    res.send(req.oidc.isAuthenticated() ? 'Logged in via Keycloak' : 'Logged out');
});

app.listen(PORT, "0.0.0.0", () => {
    console.log(`Express auth server with express-openid-connect listening on http://0.0.0.0:${PORT}`);
    console.log(`Login available at: /login`);
    console.log(`Logout available at: /logout`); 
});

From what I can understand, the error is thrown before the afterCallback function is executed.

In keycloak, I have set up Valid redirect URI as https[://]localhost:3001/callback and web origin as https[://]localhost:3001 for development

I am sorry in advance if I have made any silly errors as I have just started learning about OAuth and openid-connect protocols recently

Hi @bcodes2350

Welcome to the Auth0 Community!

I am sorry about the delayed response to your inquiry!

I was not able to find anything related to the error that you are receiving at this time. Is that the whole error message you are receiving or are there some additional elements to it?

I would recommend to check out your configuration in the .env file to make sure everything is used properly and you can also open an issue on this github page for Express Openid Connect. I will continue to investigate and come back with an update!

Kind Regards,
Nik