honojs-middleware/packages/firebase-auth/src/index.ts

93 lines
2.9 KiB
TypeScript
Raw Normal View History

import type { KeyStorer, FirebaseIdToken } from 'firebase-auth-cloudflare-workers'
2023-02-04 19:20:42 +08:00
import { Auth, WorkersKVStoreSingle } from 'firebase-auth-cloudflare-workers'
import type { Context, MiddlewareHandler } from 'hono'
import { HTTPException } from 'hono/http-exception'
2022-07-23 22:56:20 +08:00
export type VerifyFirebaseAuthEnv = {
2023-02-04 19:20:42 +08:00
PUBLIC_JWK_CACHE_KEY?: string | undefined
PUBLIC_JWK_CACHE_KV?: KVNamespace | undefined
FIREBASE_AUTH_EMULATOR_HOST: string | undefined
2022-07-23 22:56:20 +08:00
}
2022-07-28 13:58:54 +08:00
export interface VerifyFirebaseAuthConfig {
2023-02-04 19:20:42 +08:00
projectId: string
authorizationHeaderKey?: string
keyStore?: KeyStorer
keyStoreInitializer?: (c: Context) => KeyStorer
disableErrorLog?: boolean
firebaseEmulatorHost?: string
2022-07-28 13:58:54 +08:00
}
2023-02-04 19:20:42 +08:00
const defaultKVStoreJWKCacheKey = 'verify-firebase-auth-cached-public-key'
const defaultKeyStoreInitializer = (c: Context<{ Bindings: VerifyFirebaseAuthEnv }>): KeyStorer => {
if (c.env.PUBLIC_JWK_CACHE_KV === undefined) {
const res = new Response('Not Implemented', {
status: 501,
})
throw new HTTPException(501, { res })
}
2022-07-28 13:58:54 +08:00
return WorkersKVStoreSingle.getOrInitialize(
c.env.PUBLIC_JWK_CACHE_KEY ?? defaultKVStoreJWKCacheKey,
c.env.PUBLIC_JWK_CACHE_KV
2023-02-04 19:20:42 +08:00
)
}
export const verifyFirebaseAuth = (userConfig: VerifyFirebaseAuthConfig): MiddlewareHandler => {
2022-07-28 13:58:54 +08:00
const config = {
projectId: userConfig.projectId,
2023-02-04 19:20:42 +08:00
AuthorizationHeaderKey: userConfig.authorizationHeaderKey ?? 'Authorization',
2022-07-28 13:58:54 +08:00
KeyStore: userConfig.keyStore,
2023-02-04 19:20:42 +08:00
keyStoreInitializer: userConfig.keyStoreInitializer ?? defaultKeyStoreInitializer,
2022-07-28 13:58:54 +08:00
disableErrorLog: userConfig.disableErrorLog,
2022-07-29 22:03:09 +08:00
firebaseEmulatorHost: userConfig.firebaseEmulatorHost,
2023-02-04 19:20:42 +08:00
}
2022-07-28 13:58:54 +08:00
return async (c, next) => {
const authorization = c.req.raw.headers.get(config.AuthorizationHeaderKey)
2022-07-28 13:58:54 +08:00
if (authorization === null) {
const res = new Response('Bad Request', {
2022-07-28 13:58:54 +08:00
status: 400,
2023-02-04 19:20:42 +08:00
})
throw new HTTPException(400, { res })
2022-07-28 13:58:54 +08:00
}
2023-02-04 19:20:42 +08:00
const jwt = authorization.replace(/Bearer\s+/i, '')
2022-07-28 13:58:54 +08:00
const auth = Auth.getOrInitialize(
config.projectId,
config.KeyStore ?? config.keyStoreInitializer(c)
2023-02-04 19:20:42 +08:00
)
2022-07-28 13:58:54 +08:00
try {
2022-07-29 22:03:09 +08:00
const idToken = await auth.verifyIdToken(jwt, {
FIREBASE_AUTH_EMULATOR_HOST:
config.firebaseEmulatorHost ?? c.env.FIREBASE_AUTH_EMULATOR_HOST,
2023-02-04 19:20:42 +08:00
})
setFirebaseToken(c, idToken)
2022-07-28 13:58:54 +08:00
} catch (err) {
if (!userConfig.disableErrorLog) {
console.error({
2023-02-04 19:20:42 +08:00
message: 'failed to verify the requested firebase token',
2022-07-28 13:58:54 +08:00
err,
2023-02-04 19:20:42 +08:00
})
2022-07-28 13:58:54 +08:00
}
const res = new Response('Unauthorized', {
2022-07-28 13:58:54 +08:00
status: 401,
2023-02-04 19:20:42 +08:00
})
throw new HTTPException(401, { res })
2022-07-28 13:58:54 +08:00
}
2023-02-04 19:20:42 +08:00
await next()
}
}
2022-07-28 13:58:54 +08:00
2023-02-04 19:20:42 +08:00
const idTokenContextKey = 'firebase-auth-cloudflare-id-token-key'
2022-07-28 13:58:54 +08:00
2023-02-04 19:20:42 +08:00
const setFirebaseToken = (c: Context, idToken: FirebaseIdToken) => c.set(idTokenContextKey, idToken)
2022-07-28 13:58:54 +08:00
export const getFirebaseToken = (c: Context): FirebaseIdToken | null => {
2023-02-04 19:20:42 +08:00
const idToken = c.get(idTokenContextKey)
if (!idToken) {
return null
}
2023-02-04 19:20:42 +08:00
return idToken
}