46 lines
1.4 KiB
JavaScript
46 lines
1.4 KiB
JavaScript
import { createRemoteJWKSet, jwtVerify } from 'jose'
|
|
|
|
let jwks
|
|
|
|
function authConfig() {
|
|
const issuer = process.env.KEYCLOAK_ISSUER?.replace(/\/$/, '')
|
|
const clientId = process.env.KEYCLOAK_CLIENT_ID
|
|
return { issuer, clientId }
|
|
}
|
|
|
|
export function publicAuthConfig() {
|
|
const { issuer, clientId } = authConfig()
|
|
return {
|
|
authRequired: process.env.AUTH_REQUIRED !== 'false',
|
|
issuer: issuer || '',
|
|
clientId: clientId || ''
|
|
}
|
|
}
|
|
|
|
export async function requireAuth(req, res, next) {
|
|
if (process.env.AUTH_REQUIRED === 'false') {
|
|
req.user = { sub: 'local-dev', preferred_username: 'local-dev' }
|
|
return next()
|
|
}
|
|
|
|
try {
|
|
const { issuer, clientId } = authConfig()
|
|
if (!issuer || !clientId) throw new Error('Keycloak is not configured')
|
|
const header = req.headers.authorization || ''
|
|
if (!header.startsWith('Bearer ')) return res.status(401).json({ error: 'missing_bearer_token' })
|
|
|
|
jwks ||= createRemoteJWKSet(new URL(`${issuer}/protocol/openid-connect/certs`))
|
|
const { payload } = await jwtVerify(header.slice(7), jwks, { issuer })
|
|
|
|
const aud = Array.isArray(payload.aud) ? payload.aud : [payload.aud].filter(Boolean)
|
|
if (payload.azp !== clientId && !aud.includes(clientId)) {
|
|
return res.status(403).json({ error: 'wrong_client' })
|
|
}
|
|
|
|
req.user = payload
|
|
next()
|
|
} catch (error) {
|
|
res.status(401).json({ error: 'invalid_token', detail: error.message })
|
|
}
|
|
}
|