1
- import type { H3Event } from 'h3'
1
+ import type { H3Event , H3Error } from 'h3'
2
2
import { eventHandler , createError , getQuery , sendRedirect } from 'h3'
3
3
import { withQuery } from 'ufo'
4
4
import { ofetch } from 'ofetch'
5
5
import { defu } from 'defu'
6
6
import { useRuntimeConfig } from '#imports'
7
7
import type { OAuthConfig } from '#auth-utils'
8
- import { createHash , randomBytes } from 'node:crypto '
8
+ import { type OAuthChecks , checks } from '../../utils/security '
9
9
10
10
export interface OAuthOidcConfig {
11
11
/**
@@ -69,10 +69,17 @@ export interface OAuthOidcConfig {
69
69
* @example ['openid']
70
70
*/
71
71
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 [ ]
72
79
}
73
80
74
81
function validateConfig ( config : any ) {
75
- const requiredConfigKeys = [ 'clientId' , 'clientSecret' , 'authorizationUrl' , 'tokenUrl' , 'userinfoUrl' , 'redirectUri' ]
82
+ const requiredConfigKeys = [ 'clientId' , 'clientSecret' , 'authorizationUrl' , 'tokenUrl' , 'userinfoUrl' , 'redirectUri' , 'responseType' ]
76
83
const missingConfigKeys : string [ ] = [ ]
77
84
requiredConfigKeys . forEach ( key => {
78
85
if ( ! config [ key ] ) {
@@ -82,7 +89,7 @@ function validateConfig(config: any) {
82
89
if ( missingConfigKeys . length ) {
83
90
const error = createError ( {
84
91
statusCode : 500 ,
85
- message : `Missing config keys:${ missingConfigKeys . join ( ', ' ) } `
92
+ message : `Missing config keys: ${ missingConfigKeys . join ( ', ' ) } `
86
93
} )
87
94
88
95
return {
@@ -93,21 +100,11 @@ function validateConfig(config: any) {
93
100
return { valid : true }
94
101
}
95
102
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
-
105
103
export function oidcEventHandler ( { config, onSuccess, onError } : OAuthConfig < OAuthOidcConfig > ) {
106
104
return eventHandler ( async ( event : H3Event ) => {
107
- const storage = useStorage ( 'redis' )
108
105
// @ts -ignore
109
106
config = defu ( config , useRuntimeConfig ( event ) . oauth ?. oidc ) as OAuthOidcConfig
110
- const { code, state } = getQuery ( event )
107
+ const { code } = getQuery ( event )
111
108
112
109
const validationResult = validateConfig ( config )
113
110
@@ -116,12 +113,8 @@ export function oidcEventHandler({ config, onSuccess, onError }: OAuthConfig<OAu
116
113
return onError ( event , validationResult . error )
117
114
}
118
115
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
125
118
// Redirect to OIDC login page
126
119
return sendRedirect (
127
120
event ,
@@ -133,14 +126,19 @@ export function oidcEventHandler({ config, onSuccess, onError }: OAuthConfig<OAu
133
126
claims : config ?. claims || { } ,
134
127
grant_type : config . grantType || 'authorization_code' ,
135
128
audience : config . audience || null ,
136
- state : state ,
137
- code_challenge : config . codeChallengeMethod ? challenge : null ,
138
- code_challenge_method : config . codeChallengeMethod ,
129
+ ...authParams
139
130
} )
140
131
)
141
132
}
142
133
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
+ }
144
142
145
143
// @ts -ignore
146
144
const queryString = new URLSearchParams ( {
@@ -150,9 +148,10 @@ export function oidcEventHandler({ config, onSuccess, onError }: OAuthConfig<OAu
150
148
redirect_uri : config . redirectUri ,
151
149
response_type : config . responseType ,
152
150
grant_type : config . grantType || 'authorization_code' ,
153
- code_verifier : codeVerifier ,
151
+ ... checkResult
154
152
} )
155
153
154
+ // Request tokens.
156
155
const tokens : any = await ofetch (
157
156
config . tokenUrl as string ,
158
157
{
@@ -178,6 +177,8 @@ export function oidcEventHandler({ config, onSuccess, onError }: OAuthConfig<OAu
178
177
const tokenType = tokens . token_type
179
178
const accessToken = tokens . access_token
180
179
const userInfoUrl = config . userinfoUrl || ''
180
+
181
+ // Request userinfo.
181
182
const user : any = await ofetch ( userInfoUrl , {
182
183
headers : {
183
184
Authorization : `${ tokenType } ${ accessToken } `
0 commit comments