Skip to content

Commit dc94860

Browse files
authored
feat: support multiple auth0 issuers for JWT verification (#3978)
Signed-off-by: Yeganathan S <63534555+skwowet@users.noreply.github.com>
1 parent f7ad9b6 commit dc94860

4 files changed

Lines changed: 48 additions & 10 deletions

File tree

backend/.env.dist.local

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,5 +165,5 @@ CROWD_GITHUB_IS_SNOWFLAKE_ENABLED=false
165165
CROWD_TINYBIRD_BASE_URL=http://localhost:7181/
166166

167167
# Auth0
168-
CROWD_AUTH0_ISSUER_BASE_URL=
168+
CROWD_AUTH0_ISSUER_BASE_URLS=
169169
CROWD_AUTH0_AUDIENCE=

backend/config/custom-environment-variables.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@
154154
"auth0": {
155155
"clientId": "CROWD_AUTH0_CLIENT_ID",
156156
"jwks": "CROWD_AUTH0_JWKS",
157-
"issuerBaseURL": "CROWD_AUTH0_ISSUER_BASE_URL",
157+
"issuerBaseURLs": "CROWD_AUTH0_ISSUER_BASE_URLS",
158158
"audience": "CROWD_AUTH0_AUDIENCE"
159159
},
160160
"sso": {

backend/src/api/public/middlewares/oauth2Middleware.ts

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,17 @@ import { UnauthorizedError } from '@crowd/common'
66
import type { Auth0Configuration } from '@/conf/configTypes'
77
import type { Auth0TokenPayload } from '@/types/api'
88

9+
function resolveIssuer(req: Request): string | undefined {
10+
const token = req.headers.authorization?.split(' ')[1]
11+
if (!token) return undefined
12+
try {
13+
const { iss } = JSON.parse(Buffer.from(token.split('.')[1], 'base64url').toString())
14+
return typeof iss === 'string' ? iss : undefined
15+
} catch {
16+
return undefined
17+
}
18+
}
19+
920
function resolveActor(req: Request, _res: Response, next: NextFunction): void {
1021
const payload = (req.auth?.payload ?? {}) as Auth0TokenPayload
1122

@@ -26,11 +37,38 @@ function resolveActor(req: Request, _res: Response, next: NextFunction): void {
2637
}
2738

2839
export function oauth2Middleware(config: Auth0Configuration): RequestHandler[] {
29-
return [
30-
auth({
31-
issuerBaseURL: config.issuerBaseURL,
32-
audience: config.audience,
33-
}),
34-
resolveActor,
35-
]
40+
const issuers = config.issuerBaseURLs
41+
.split(',')
42+
.map((s) => s.trim())
43+
.filter(Boolean)
44+
45+
if (issuers.length === 0) {
46+
throw new Error('No auth0 issuers configured')
47+
}
48+
49+
const handlersByIssuer = new Map(
50+
issuers.map((issuerBaseURL) => [
51+
issuerBaseURL.replace(/\/$/, ''),
52+
auth({ issuerBaseURL, audience: config.audience }),
53+
]),
54+
)
55+
56+
const verifyJwt: RequestHandler = (req, res, next) => {
57+
const iss = resolveIssuer(req)
58+
if (!iss) {
59+
next(new UnauthorizedError('Missing or malformed bearer token'))
60+
return
61+
}
62+
63+
const handler = handlersByIssuer.get(iss.replace(/\/$/, ''))
64+
65+
if (!handler) {
66+
next(new UnauthorizedError('Unknown token issuer'))
67+
return
68+
}
69+
70+
handler(req, res, next)
71+
}
72+
73+
return [verifyJwt, resolveActor]
3674
}

backend/src/conf/configTypes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ export interface ApiConfiguration {
6868
export interface Auth0Configuration {
6969
clientId: string
7070
jwks: string
71-
issuerBaseURL: string
71+
issuerBaseURLs: string
7272
audience: string
7373
}
7474

0 commit comments

Comments
 (0)