I have a next.js app running on a server, (currently not running the node app in docker as doing this broke the expected behaviour of role based auth, separate topic for that issue). It is running on the bare metal at port 3001.
I also have a keycloak auth server running inside a docker container on the same machine at port 8080.
the app is behind the reverse proxy at (fake hostname given here)
subdomain.domain.io
and keycloak is running at
subdomain.domain.io/authorisation
when I run my node app on my local machine and configure the keycloak.json to use the subdomain.domain.io/authorisation auth server it all works as expected.
When I deploy the node app on the remote machine (inside or outside docker) behind the reverse proxy, it randomly logs me out on certain page changes as if the session / cookies are getting lost behind nginx.
I have been stuck on this for weeks… There is nothing of note logged by either keycloak, nginx or the app itself when the logout occurs.
Here is the configuration of keycloak in the docker container
keycloak:
# restart: always
container_name: "keycloak-server"
image: keycloak/keycloak:latest
ports:
- "8080:8080"
- "8443:8443"
environment:
TZ: "Europe/London"
#~~~~~~~~~~~~~~~~~# User Settings
KEYCLOAK_ADMIN: ${KEYCLOAK_ADMIN:-admin}
KEYCLOAK_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD:-password}
KC_LOG_LEVEL: info
KC_HEALTH_ENABLED: true
#~~~~~~~~~~~~~~~~~#
KC_HOSTNAME: "https://subdomain.domain.io/authorisation"
KC_HOSTNAME_ADMIN: "https://admin.subdomain.domain.io/authorisation"
KC_HOSTNAME_BACKCHANNEL_DYNAMIC: false # needed to allow other containers to commumicate with keycloak server-side
KC_HTTP_ENABLED: true ## assume this is edge server, used between nginx and kc
KC_HOSTNAME_DEBUG: true
#~~~~~~~~~~~~~~~~~#
KC_PROXY_HEADERS: xforwarded ## enables parsing of non-standard X-Forwarded-* headers, such as X-Forwarded-For, X-Forwarded-Proto, X-Forwarded-Host, and X-Forwarded-Port`
KC_DB: postgres
KC_DB_USERNAME: postgres
KC_DB_PASSWORD: ${POSTGRES_PASSWORD:-password}
KC_DB_POOL_MAX_SIZE: 50
KC_DB_URL_HOST: postgres
command: start --import-realm # --optimized can be added if build has already happened
volumes:
- ./realm:/opt/keycloak/data/import:ro # the realm to import
depends_on:
- postgres
networks:
- auth-network
The client settings in the admin console have standard flow and direct access grants enabled only. I believe all the access settings are correctly allowing the right origin and redirect URLs.
Here is the server.js code, where I think the problem might be, I followed the keycloak docs for nodejs in writing this
app.prepare().then(() => {
const server = express();
if (keycloakEnabled) { // do keycloak auth stuff if env var is set
console.log('the following pages require keycloak authentication', process.env.PROTECTED_PAGES ? colourYellow : colourRed, process.env.PROTECTED_PAGES, colourReset)
console.log('the following pages require the', process.env.ROLE ? colourYellow : colourRed, process.env.ROLE, colourReset, 'role: ', process.env.ROLE_PROTECTED_PAGES ? colourYellow : colourRed, process.env.ROLE_PROTECTED_PAGES, colourReset)
server.set('trust proxy', true); // the client’s IP address is understood as the left-most entry in the X-Forwarded-For header.
if (!dev) {
let redisClient;
console.log(`development mode is:`, colourGreen, dev, colourReset, `-> connecting to redis session store at`, colourGreen, `${redisHost}:${redisPort}`, colourReset);
try {
redisClient = createClient({
socket: {
host: redisHost,
port: redisPort
}
});
} catch (error) {
console.log('Error while creating Redis Client, please ensure that Redis is running and the host is specified as an environment variable if this viz app is in a Docker container');
console.error(error);
}
redisClient.connect().catch('Error while creating Redis Client, please ensure that Redis is running and the host is specified as an environment variable if this viz app is in a Docker container', console.error);
store = new RedisStore({
client: redisClient,
prefix: "redis",
ttl: undefined,
});
} else {
store = new MemoryStore(); // use in-memory store for session data in dev mode
console.log(`development mode is:`, dev ? colourYellow : colourRed, dev, colourReset, `-> using in-memory session store (express-session MemoryStore())`);
}
server.use(
session({
secret: 'login',
resave: false,
saveUninitialized: true,
store: store,
// cookie: {
// secure: !dev, // set to true if using https
// maxAge: 24 * 60 * 60 * 1000, // 1 day
// sameSite: 'lax', // adjust as needed
// domain: 'bnl.theworldavatar.io' // ensure this matches your domain
// }
})
);
const keycloak = new Keycloak({ store: store });
server.use(keycloak.middleware());
server.get('/api/userinfo', keycloak.protect(), (req, res) => {
const { preferred_username: userName, given_name: firstName, family_name: lastName, name: fullName, realm_access: { roles }, resource_access: clientRoles } = req.kauth.grant.access_token.content;
res.json({ userName, firstName, lastName, fullName, roles, clientRoles });
});
server.get('/logout', (req, res) => {
req.logout(); // Keycloak adapter logout
req.session.destroy(() => { // This destroys the session
res.clearCookie('connect.sid', { path: '/' }); // Clear the session cookie
});
});
const protectedPages = process.env.PROTECTED_PAGES.split(',');
protectedPages.forEach(page => {
server.get(page, keycloak.protect());
});
const roleProtectedPages = process.env.ROLE_PROTECTED_PAGES.split(',');
roleProtectedPages.forEach(page => {
server.get(page, keycloak.protect(process.env.ROLE));
console.log('protecting page', page, 'with role', process.env.ROLE);
});
}
and keycloak.json in the node project folder
{
"realm": "REALM",
"auth-server-url": "https://subdomain.domain.io/authorisation",
"ssl-required": "external",
"resource": "job-portal",
"public-client": true,
"use-resource-role-mappings": true,
"confidential-port": 0
}
If the problem is not there then I suppose it could also be the nginx config (this is config for the subdomain
###########################################################################
## --------------------------------------------------------------------- ##
## --------------------------------- BNL ------------------------------- ##
## --------------------------------------------------------------------- ##
###########################################################################
location / {
proxy_pass http://192.168.1.xxx:3001/;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Forwarded-Server $http_host;
proxy_set_header X-Forwarded-Proto https;
}
########################### keycloak
location /authorisation/realms/ {
proxy_pass http://192.168.1.xxx:8080/realms/;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /authorisation/resources/ {
proxy_pass http://192.168.1.xxx:8080/resources/;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /authorisation/robots.txt {
proxy_pass http://192.168.1.xxx:8080/robots.txt/;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
proxy_set_header X-Forwarded-Proto $scheme;
}
Sorry for the long post, but I am completely at a loss as to why my users keep getting logged out. I’ve been stuck on this for weeks and would be so grateful for any help!