Cloudflareのworkersのitty-router-openapiでauthorizationヘッダで認証する
経緯
- itty-router-openapiのendpointのschemaのauthorizationヘッダを追加した。
- OpenAPIページのI/Fに表示されているが、OpenAPIページからAPIを実行しても送信されていない。
- curlコマンドでauthorizationヘッダありでAPIを実行すると、API側では期待通りの動作をする。
OpenAPIページからauthorizationヘッダが送信されていない。
authorizationヘッダは、endpointのschemaに定義するは間違い。
itty-router-openapiでの認証の方法
security componentを使用する
Security
全てのエンドポイントで認証を必要をする場合の設定
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
export const router = OpenAPIRouter({
docs_url: "/",
schema: {
security: [
{
BearerAuth: [],
},
],
},
});
router.registry.registerComponent(
'securitySchemes',
'BearerAuth',
{
type: 'http',
scheme: 'bearer',
},
)
// 認証処理を行う
router.all('/*', Authenticate)
// 認証が必要なエンドポイントはAuthenticateの後
router.get('something-endpoint', SomethingEndpooint)
|
Authorizeの鍵マークが表示されるようになり、OpenAPIページからauthorizationヘッダが送信されるようになった。
認証処理の例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
|
import * as jose from "jose";
import {JWTVerifyOptions} from "jose/dist/types/jwt/verify";
import {z} from 'zod'
const userSchema = z.object({
sub: z.string(),
name: z.string(),
nickname: z.string(),
picture: z.string(),
email: z.string(),
email_verified: z.boolean(),
updated_at: z.string(),
});
const getBearer = (request: Request): null | string => {
const authHeader = request.headers.get('authorization')
if (!authHeader || authHeader.substring(0, 6) !== 'Bearer') {
return null
}
return authHeader.substring(6).trim()
}
const verify = async (token: string, env: any) => {
const JWKS = await jose.createRemoteJWKSet(new URL(env.JWKS_URL))
const options:JWTVerifyOptions = {
issuer: env.JWT_ISSUER,
audience: env.JWT_AUDIENCE,
}
const {payload, protectedHeader} =
await jose.jwtVerify(token, JWKS, options).catch(async (error) => {
if (error?.code === 'ERR_JWKS_MULTIPLE_MATCHING_KEYS') {
for await (const publicKey of error) {
try {
return await jose.jwtVerify(token, publicKey, options)
} catch (innerError) {
if (innerError?.code === 'ERR_JWS_SIGNATURE_VERIFICATION_FAILED') {
continue
}
throw innerError
}
}
throw new jose.errors.JWSSignatureVerificationFailed()
}
throw error
})
return {payload, protectedHeader}
}
const getUserInfo = async (token: string, env: any,) => {
const payload = jose.decodeJwt(token)
let userInfoEndPoint:string
if (Array.isArray(payload.aud)) {
userInfoEndPoint = payload.aud.find(e => e.endsWith("userinfo"))
} else {
if (payload.aud.endsWith("userinfo")) {
userInfoEndPoint = payload.aud
}
}
if (userInfoEndPoint) {
const res = await fetch(userInfoEndPoint, {
method: 'GET',
headers: {
'Authorization': 'Bearer ' + token,
}
})
if (res.ok) {
const validatedUser = userSchema.safeParse(await res.json())
if (!validatedUser.success) {
throw new Error("Unable to retrieve user information");
}
if (!validatedUser.data.email_verified) {
throw new Error("User Email not verified");
}
return validatedUser.data
}
}
}
export async function Authenticate(request: Request, env: any, context: any) {
const token = getBearer(request)
if (!token) {
throw new Error("token not found");
}
try {
const {payload, protectedHeader} = await verify(token, env)
} catch(error) {
if (!(error instanceof CustomHttpStatus)) {
throw new Error("token not verified");
}
}
const user = await getUserInfo(token, env)
env.user = user
return
}
|