Skip to content

Commit ebe364d

Browse files
fix: integrate security utils.
1 parent 9354a72 commit ebe364d

File tree

2 files changed

+32
-31
lines changed

2 files changed

+32
-31
lines changed

src/runtime/server/lib/oauth/oidc.ts

Lines changed: 27 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import type { H3Event } from 'h3'
1+
import type { H3Event, H3Error } from 'h3'
22
import { eventHandler, createError, getQuery, sendRedirect } from 'h3'
33
import { withQuery } from 'ufo'
44
import { ofetch } from 'ofetch'
55
import { defu } from 'defu'
66
import { useRuntimeConfig } from '#imports'
77
import type { OAuthConfig } from '#auth-utils'
8-
import { createHash, randomBytes } from 'node:crypto'
8+
import { type OAuthChecks, checks } from '../../utils/security'
99

1010
export interface OAuthOidcConfig {
1111
/**
@@ -69,10 +69,17 @@ export interface OAuthOidcConfig {
6969
* @example ['openid']
7070
*/
7171
scope?: string[]
72+
/**
73+
* checks
74+
* @default []
75+
* @see https://auth0.com/docs/flows/authorization-code-flow-with-proof-key-for-code-exchange-pkce
76+
* @see https://auth0.com/docs/protocols/oauth2/oauth-state
77+
*/
78+
checks?: OAuthChecks[]
7279
}
7380

7481
function validateConfig(config: any) {
75-
const requiredConfigKeys = ['clientId', 'clientSecret', 'authorizationUrl', 'tokenUrl', 'userinfoUrl', 'redirectUri']
82+
const requiredConfigKeys = ['clientId', 'clientSecret', 'authorizationUrl', 'tokenUrl', 'userinfoUrl', 'redirectUri', 'responseType']
7683
const missingConfigKeys: string[] = []
7784
requiredConfigKeys.forEach(key => {
7885
if (!config[key]) {
@@ -82,7 +89,7 @@ function validateConfig(config: any) {
8289
if (missingConfigKeys.length) {
8390
const error = createError({
8491
statusCode: 500,
85-
message: `Missing config keys:${missingConfigKeys.join(', ')}`
92+
message: `Missing config keys: ${missingConfigKeys.join(', ')}`
8693
})
8794

8895
return {
@@ -93,21 +100,11 @@ function validateConfig(config: any) {
93100
return { valid: true }
94101
}
95102

96-
function createCodeChallenge(verifier: string) {
97-
return createHash('sha256')
98-
.update(verifier)
99-
.digest('base64')
100-
.replace(/\+/g, '-')
101-
.replace(/\//g, '_')
102-
.replace(/=+$/, '')
103-
}
104-
105103
export function oidcEventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthOidcConfig>) {
106104
return eventHandler(async (event: H3Event) => {
107-
const storage = useStorage('redis')
108105
// @ts-ignore
109106
config = defu(config, useRuntimeConfig(event).oauth?.oidc) as OAuthOidcConfig
110-
const { code, state } = getQuery(event)
107+
const { code } = getQuery(event)
111108

112109
const validationResult = validateConfig(config)
113110

@@ -116,12 +113,8 @@ export function oidcEventHandler({ config, onSuccess, onError }: OAuthConfig<OAu
116113
return onError(event, validationResult.error)
117114
}
118115

119-
if (!code && !state) {
120-
const state = randomBytes(10).toString('hex')
121-
const codeVerifier = randomBytes(52).toString('hex')
122-
const challenge = createCodeChallenge(codeVerifier)
123-
await storage.setItem('oidc:verifier:' + state, codeVerifier)
124-
await storage.setItem('oidc:challenge:' + state, challenge)
116+
if (!code) {
117+
const authParams = await checks.create(event, config.checks) // Initialize checks
125118
// Redirect to OIDC login page
126119
return sendRedirect(
127120
event,
@@ -133,14 +126,19 @@ export function oidcEventHandler({ config, onSuccess, onError }: OAuthConfig<OAu
133126
claims: config?.claims || {},
134127
grant_type: config.grantType || 'authorization_code',
135128
audience: config.audience || null,
136-
state: state,
137-
code_challenge: config.codeChallengeMethod ? challenge : null,
138-
code_challenge_method: config.codeChallengeMethod,
129+
...authParams
139130
})
140131
)
141132
}
142133

143-
const codeVerifier: string = await storage.getItem('oidc:verifier:' + state) || ''
134+
// Verify checks
135+
let checkResult
136+
try {
137+
checkResult = await checks.use(event, config.checks)
138+
} catch (error) {
139+
if (!onError) throw error
140+
return onError(event, error as H3Error)
141+
}
144142

145143
// @ts-ignore
146144
const queryString = new URLSearchParams({
@@ -150,9 +148,10 @@ export function oidcEventHandler({ config, onSuccess, onError }: OAuthConfig<OAu
150148
redirect_uri: config.redirectUri,
151149
response_type: config.responseType,
152150
grant_type: config.grantType || 'authorization_code',
153-
code_verifier: codeVerifier,
151+
...checkResult
154152
})
155153

154+
// Request tokens.
156155
const tokens: any = await ofetch(
157156
config.tokenUrl as string,
158157
{
@@ -178,6 +177,8 @@ export function oidcEventHandler({ config, onSuccess, onError }: OAuthConfig<OAu
178177
const tokenType = tokens.token_type
179178
const accessToken = tokens.access_token
180179
const userInfoUrl = config.userinfoUrl || ''
180+
181+
// Request userinfo.
181182
const user: any = await ofetch(userInfoUrl, {
182183
headers: {
183184
Authorization: `${tokenType} ${accessToken}`

src/runtime/server/utils/security.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { subtle, getRandomValues } from 'uncrypto'
33

44
export type OAuthChecks = 'pkce' | 'state'
55

6-
// From oauth4webapi https://github.com/panva/oauth4webapi/blob/4b46a7b4a4ca77a513774c94b718592fe3ad576f/src/index.ts#L567C1-L579C2
6+
// From oauth4webapi https://github.com/panva/oauth4webapi/blob/4b46a7b4a4ca77a513774c94b718592fe3ad576f/src/index.ts#L567C1-L579C2
77
const CHUNK_SIZE = 0x8000
88
export function encodeBase64Url(input: Uint8Array | ArrayBuffer) {
99
if (input instanceof ArrayBuffer) {
@@ -22,16 +22,16 @@ function randomBytes() {
2222
return encodeBase64Url(getRandomValues(new Uint8Array(32)))
2323
}
2424

25-
/**
25+
/**
2626
* Generate a random `code_verifier` for use in the PKCE flow
2727
* @see https://tools.ietf.org/html/rfc7636#section-4.1
2828
*/
2929
export function generateCodeVerifier() {
3030
return randomBytes()
3131
}
3232

33-
/**
34-
* Generate a random `state` used to prevent CSRF attacks
33+
/**
34+
* Generate a random `state` used to prevent CSRF attacks
3535
* @see https://www.rfc-editor.org/rfc/rfc6749.html#section-4.1.1
3636
*/
3737
export function generateState() {
@@ -112,4 +112,4 @@ export const checks = {
112112
}
113113
return res
114114
},
115-
}
115+
}

0 commit comments

Comments
 (0)