@_mustachio/openauth 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (192) hide show
  1. package/dist/esm/client.js +186 -0
  2. package/dist/esm/css.d.js +0 -0
  3. package/dist/esm/error.js +73 -0
  4. package/dist/esm/index.js +14 -0
  5. package/dist/esm/issuer.js +558 -0
  6. package/dist/esm/jwt.js +16 -0
  7. package/dist/esm/keys.js +113 -0
  8. package/dist/esm/pkce.js +35 -0
  9. package/dist/esm/provider/apple.js +28 -0
  10. package/dist/esm/provider/arctic.js +43 -0
  11. package/dist/esm/provider/code.js +58 -0
  12. package/dist/esm/provider/cognito.js +16 -0
  13. package/dist/esm/provider/discord.js +15 -0
  14. package/dist/esm/provider/facebook.js +24 -0
  15. package/dist/esm/provider/github.js +15 -0
  16. package/dist/esm/provider/google.js +25 -0
  17. package/dist/esm/provider/index.js +3 -0
  18. package/dist/esm/provider/jumpcloud.js +15 -0
  19. package/dist/esm/provider/keycloak.js +15 -0
  20. package/dist/esm/provider/linkedin.js +15 -0
  21. package/dist/esm/provider/m2m.js +17 -0
  22. package/dist/esm/provider/microsoft.js +24 -0
  23. package/dist/esm/provider/oauth2.js +119 -0
  24. package/dist/esm/provider/oidc.js +69 -0
  25. package/dist/esm/provider/passkey.js +315 -0
  26. package/dist/esm/provider/password.js +306 -0
  27. package/dist/esm/provider/provider.js +10 -0
  28. package/dist/esm/provider/slack.js +15 -0
  29. package/dist/esm/provider/spotify.js +15 -0
  30. package/dist/esm/provider/twitch.js +15 -0
  31. package/dist/esm/provider/x.js +16 -0
  32. package/dist/esm/provider/yahoo.js +15 -0
  33. package/dist/esm/random.js +27 -0
  34. package/dist/esm/storage/aws.js +39 -0
  35. package/dist/esm/storage/cloudflare.js +42 -0
  36. package/dist/esm/storage/dynamo.js +116 -0
  37. package/dist/esm/storage/memory.js +88 -0
  38. package/dist/esm/storage/storage.js +36 -0
  39. package/dist/esm/subject.js +7 -0
  40. package/dist/esm/ui/base.js +407 -0
  41. package/dist/esm/ui/code.js +151 -0
  42. package/dist/esm/ui/form.js +43 -0
  43. package/dist/esm/ui/icon.js +92 -0
  44. package/dist/esm/ui/passkey.js +329 -0
  45. package/dist/esm/ui/password.js +338 -0
  46. package/dist/esm/ui/select.js +187 -0
  47. package/dist/esm/ui/theme.js +115 -0
  48. package/dist/esm/util.js +54 -0
  49. package/dist/types/client.d.ts +466 -0
  50. package/dist/types/client.d.ts.map +1 -0
  51. package/dist/types/error.d.ts +77 -0
  52. package/dist/types/error.d.ts.map +1 -0
  53. package/dist/types/index.d.ts +20 -0
  54. package/dist/types/index.d.ts.map +1 -0
  55. package/dist/types/issuer.d.ts +465 -0
  56. package/dist/types/issuer.d.ts.map +1 -0
  57. package/dist/types/jwt.d.ts +6 -0
  58. package/dist/types/jwt.d.ts.map +1 -0
  59. package/dist/types/keys.d.ts +18 -0
  60. package/dist/types/keys.d.ts.map +1 -0
  61. package/dist/types/pkce.d.ts +7 -0
  62. package/dist/types/pkce.d.ts.map +1 -0
  63. package/dist/types/provider/apple.d.ts +108 -0
  64. package/dist/types/provider/apple.d.ts.map +1 -0
  65. package/dist/types/provider/arctic.d.ts +16 -0
  66. package/dist/types/provider/arctic.d.ts.map +1 -0
  67. package/dist/types/provider/code.d.ts +74 -0
  68. package/dist/types/provider/code.d.ts.map +1 -0
  69. package/dist/types/provider/cognito.d.ts +64 -0
  70. package/dist/types/provider/cognito.d.ts.map +1 -0
  71. package/dist/types/provider/discord.d.ts +38 -0
  72. package/dist/types/provider/discord.d.ts.map +1 -0
  73. package/dist/types/provider/facebook.d.ts +74 -0
  74. package/dist/types/provider/facebook.d.ts.map +1 -0
  75. package/dist/types/provider/github.d.ts +38 -0
  76. package/dist/types/provider/github.d.ts.map +1 -0
  77. package/dist/types/provider/google.d.ts +74 -0
  78. package/dist/types/provider/google.d.ts.map +1 -0
  79. package/dist/types/provider/index.d.ts +4 -0
  80. package/dist/types/provider/index.d.ts.map +1 -0
  81. package/dist/types/provider/jumpcloud.d.ts +38 -0
  82. package/dist/types/provider/jumpcloud.d.ts.map +1 -0
  83. package/dist/types/provider/keycloak.d.ts +67 -0
  84. package/dist/types/provider/keycloak.d.ts.map +1 -0
  85. package/dist/types/provider/linkedin.d.ts +6 -0
  86. package/dist/types/provider/linkedin.d.ts.map +1 -0
  87. package/dist/types/provider/m2m.d.ts +34 -0
  88. package/dist/types/provider/m2m.d.ts.map +1 -0
  89. package/dist/types/provider/microsoft.d.ts +89 -0
  90. package/dist/types/provider/microsoft.d.ts.map +1 -0
  91. package/dist/types/provider/oauth2.d.ts +133 -0
  92. package/dist/types/provider/oauth2.d.ts.map +1 -0
  93. package/dist/types/provider/oidc.d.ts +91 -0
  94. package/dist/types/provider/oidc.d.ts.map +1 -0
  95. package/dist/types/provider/passkey.d.ts +143 -0
  96. package/dist/types/provider/passkey.d.ts.map +1 -0
  97. package/dist/types/provider/password.d.ts +210 -0
  98. package/dist/types/provider/password.d.ts.map +1 -0
  99. package/dist/types/provider/provider.d.ts +29 -0
  100. package/dist/types/provider/provider.d.ts.map +1 -0
  101. package/dist/types/provider/slack.d.ts +59 -0
  102. package/dist/types/provider/slack.d.ts.map +1 -0
  103. package/dist/types/provider/spotify.d.ts +38 -0
  104. package/dist/types/provider/spotify.d.ts.map +1 -0
  105. package/dist/types/provider/twitch.d.ts +38 -0
  106. package/dist/types/provider/twitch.d.ts.map +1 -0
  107. package/dist/types/provider/x.d.ts +38 -0
  108. package/dist/types/provider/x.d.ts.map +1 -0
  109. package/dist/types/provider/yahoo.d.ts +38 -0
  110. package/dist/types/provider/yahoo.d.ts.map +1 -0
  111. package/dist/types/random.d.ts +3 -0
  112. package/dist/types/random.d.ts.map +1 -0
  113. package/dist/types/storage/aws.d.ts +4 -0
  114. package/dist/types/storage/aws.d.ts.map +1 -0
  115. package/dist/types/storage/cloudflare.d.ts +34 -0
  116. package/dist/types/storage/cloudflare.d.ts.map +1 -0
  117. package/dist/types/storage/dynamo.d.ts +65 -0
  118. package/dist/types/storage/dynamo.d.ts.map +1 -0
  119. package/dist/types/storage/memory.d.ts +49 -0
  120. package/dist/types/storage/memory.d.ts.map +1 -0
  121. package/dist/types/storage/storage.d.ts +15 -0
  122. package/dist/types/storage/storage.d.ts.map +1 -0
  123. package/dist/types/subject.d.ts +122 -0
  124. package/dist/types/subject.d.ts.map +1 -0
  125. package/dist/types/ui/base.d.ts +5 -0
  126. package/dist/types/ui/base.d.ts.map +1 -0
  127. package/dist/types/ui/code.d.ts +104 -0
  128. package/dist/types/ui/code.d.ts.map +1 -0
  129. package/dist/types/ui/form.d.ts +6 -0
  130. package/dist/types/ui/form.d.ts.map +1 -0
  131. package/dist/types/ui/icon.d.ts +6 -0
  132. package/dist/types/ui/icon.d.ts.map +1 -0
  133. package/dist/types/ui/passkey.d.ts +5 -0
  134. package/dist/types/ui/passkey.d.ts.map +1 -0
  135. package/dist/types/ui/password.d.ts +139 -0
  136. package/dist/types/ui/password.d.ts.map +1 -0
  137. package/dist/types/ui/select.d.ts +55 -0
  138. package/dist/types/ui/select.d.ts.map +1 -0
  139. package/dist/types/ui/theme.d.ts +207 -0
  140. package/dist/types/ui/theme.d.ts.map +1 -0
  141. package/dist/types/util.d.ts +8 -0
  142. package/dist/types/util.d.ts.map +1 -0
  143. package/package.json +51 -0
  144. package/src/client.ts +749 -0
  145. package/src/css.d.ts +4 -0
  146. package/src/error.ts +120 -0
  147. package/src/index.ts +26 -0
  148. package/src/issuer.ts +1302 -0
  149. package/src/jwt.ts +17 -0
  150. package/src/keys.ts +139 -0
  151. package/src/pkce.ts +40 -0
  152. package/src/provider/apple.ts +127 -0
  153. package/src/provider/arctic.ts +66 -0
  154. package/src/provider/code.ts +227 -0
  155. package/src/provider/cognito.ts +74 -0
  156. package/src/provider/discord.ts +45 -0
  157. package/src/provider/facebook.ts +84 -0
  158. package/src/provider/github.ts +45 -0
  159. package/src/provider/google.ts +85 -0
  160. package/src/provider/index.ts +3 -0
  161. package/src/provider/jumpcloud.ts +45 -0
  162. package/src/provider/keycloak.ts +75 -0
  163. package/src/provider/linkedin.ts +12 -0
  164. package/src/provider/m2m.ts +56 -0
  165. package/src/provider/microsoft.ts +100 -0
  166. package/src/provider/oauth2.ts +297 -0
  167. package/src/provider/oidc.ts +179 -0
  168. package/src/provider/passkey.ts +655 -0
  169. package/src/provider/password.ts +672 -0
  170. package/src/provider/provider.ts +33 -0
  171. package/src/provider/slack.ts +67 -0
  172. package/src/provider/spotify.ts +45 -0
  173. package/src/provider/twitch.ts +45 -0
  174. package/src/provider/x.ts +46 -0
  175. package/src/provider/yahoo.ts +45 -0
  176. package/src/random.ts +24 -0
  177. package/src/storage/aws.ts +59 -0
  178. package/src/storage/cloudflare.ts +77 -0
  179. package/src/storage/dynamo.ts +193 -0
  180. package/src/storage/memory.ts +135 -0
  181. package/src/storage/storage.ts +46 -0
  182. package/src/subject.ts +130 -0
  183. package/src/ui/base.tsx +118 -0
  184. package/src/ui/code.tsx +215 -0
  185. package/src/ui/form.tsx +40 -0
  186. package/src/ui/icon.tsx +95 -0
  187. package/src/ui/passkey.tsx +321 -0
  188. package/src/ui/password.tsx +405 -0
  189. package/src/ui/select.tsx +221 -0
  190. package/src/ui/theme.ts +319 -0
  191. package/src/ui/ui.css +252 -0
  192. package/src/util.ts +58 -0
@@ -0,0 +1,297 @@
1
+ /**
2
+ * Use this to connect authentication providers that support OAuth 2.0.
3
+ *
4
+ * ```ts {5-12}
5
+ * import { Oauth2Provider } from "@openauthjs/openauth/provider/oauth2"
6
+ *
7
+ * export default issuer({
8
+ * providers: {
9
+ * oauth2: Oauth2Provider({
10
+ * clientID: "1234567890",
11
+ * clientSecret: "0987654321",
12
+ * endpoint: {
13
+ * authorization: "https://auth.myserver.com/authorize",
14
+ * token: "https://auth.myserver.com/token"
15
+ * }
16
+ * })
17
+ * }
18
+ * })
19
+ * ```
20
+ *
21
+ *
22
+ * @packageDocumentation
23
+ */
24
+
25
+ import { createRemoteJWKSet, jwtVerify } from "jose"
26
+ import { OauthError } from "../error.js"
27
+ import { generatePKCE } from "../pkce.js"
28
+ import { getRelativeUrl } from "../util.js"
29
+ import { Provider } from "./provider.js"
30
+
31
+ export interface Oauth2Config {
32
+ /**
33
+ * @internal
34
+ */
35
+ type?: string
36
+ /**
37
+ * The client ID.
38
+ *
39
+ * This is just a string to identify your app.
40
+ *
41
+ * @example
42
+ * ```ts
43
+ * {
44
+ * clientID: "my-client"
45
+ * }
46
+ * ```
47
+ */
48
+ clientID: string
49
+ /**
50
+ * The client secret.
51
+ *
52
+ * This is a private key that's used to authenticate your app. It should be kept secret.
53
+ *
54
+ * @example
55
+ * ```ts
56
+ * {
57
+ * clientSecret: "0987654321"
58
+ * }
59
+ * ```
60
+ */
61
+ clientSecret: string
62
+ /**
63
+ * The URLs of the authorization and token endpoints.
64
+ *
65
+ * @example
66
+ * ```ts
67
+ * {
68
+ * endpoint: {
69
+ * authorization: "https://auth.myserver.com/authorize",
70
+ * token: "https://auth.myserver.com/token",
71
+ * jwks: "https://auth.myserver.com/auth/keys"
72
+ * }
73
+ * }
74
+ * ```
75
+ */
76
+ endpoint: {
77
+ /**
78
+ * The URL of the authorization endpoint.
79
+ */
80
+ authorization: string
81
+ /**
82
+ * The URL of the token endpoint.
83
+ */
84
+ token: string
85
+ /**
86
+ * The URL of the JWKS endpoint.
87
+ */
88
+ jwks?: string
89
+ }
90
+ /**
91
+ * A list of OAuth scopes that you want to request.
92
+ *
93
+ * @example
94
+ * ```ts
95
+ * {
96
+ * scopes: ["email", "profile"]
97
+ * }
98
+ * ```
99
+ */
100
+ scopes: string[]
101
+ /**
102
+ * Whether to use PKCE (Proof Key for Code Exchange) for the authorization code flow.
103
+ * Some providers like x.com require this.
104
+ * @default false
105
+ */
106
+ pkce?: boolean
107
+ /**
108
+ * Any additional parameters that you want to pass to the authorization endpoint.
109
+ * @example
110
+ * ```ts
111
+ * {
112
+ * query: {
113
+ * access_type: "offline",
114
+ * prompt: "consent"
115
+ * }
116
+ * }
117
+ * ```
118
+ */
119
+ query?: Record<string, string>
120
+ }
121
+
122
+ /**
123
+ * @internal
124
+ */
125
+ export type Oauth2WrappedConfig = Omit<Oauth2Config, "endpoint" | "name">
126
+
127
+ /**
128
+ * @internal
129
+ */
130
+ export interface Oauth2Token {
131
+ access: string
132
+ refresh: string
133
+ expiry: number
134
+ id?: Record<string, any>
135
+ raw: Record<string, any>
136
+ }
137
+
138
+ interface ProviderState {
139
+ state: string
140
+ redirect: string
141
+ codeVerifier?: string
142
+ }
143
+
144
+ export function Oauth2Provider(
145
+ config: Oauth2Config,
146
+ ): Provider<{ tokenset: Oauth2Token; clientID: string }> {
147
+ const query = config.query || {}
148
+
149
+ // Helper function to handle token exchange and response building
150
+ async function handleCallbackLogic(
151
+ c: any,
152
+ ctx: any,
153
+ provider: ProviderState,
154
+ code: string | undefined,
155
+ ) {
156
+ if (!provider || !code) {
157
+ return c.redirect(getRelativeUrl(c, "./authorize"))
158
+ }
159
+
160
+ const body = new URLSearchParams({
161
+ client_id: config.clientID,
162
+ client_secret: config.clientSecret,
163
+ code,
164
+ grant_type: "authorization_code",
165
+ redirect_uri: provider.redirect,
166
+ ...(provider.codeVerifier
167
+ ? { code_verifier: provider.codeVerifier }
168
+ : {}),
169
+ })
170
+
171
+ const json: any = await fetch(config.endpoint.token, {
172
+ method: "POST",
173
+ headers: {
174
+ "Content-Type": "application/x-www-form-urlencoded",
175
+ Accept: "application/json",
176
+ },
177
+ body: body.toString(),
178
+ }).then((r) => r.json())
179
+
180
+ if ("error" in json) {
181
+ throw new OauthError(json.error, json.error_description)
182
+ }
183
+
184
+ let idTokenPayload: Record<string, any> | null = null
185
+ if (config.endpoint.jwks) {
186
+ const jwksEndpoint = new URL(config.endpoint.jwks)
187
+ // @ts-expect-error bun/node mismatch
188
+ const jwks = createRemoteJWKSet(jwksEndpoint)
189
+ const { payload } = await jwtVerify(json.id_token, jwks, {
190
+ audience: config.clientID,
191
+ })
192
+ idTokenPayload = payload
193
+ }
194
+
195
+ return ctx.success(c, {
196
+ clientID: config.clientID,
197
+ tokenset: {
198
+ get access() {
199
+ return json.access_token
200
+ },
201
+ get refresh() {
202
+ return json.refresh_token
203
+ },
204
+ get expiry() {
205
+ return json.expires_in
206
+ },
207
+ get id() {
208
+ if (!idTokenPayload) return null
209
+ return idTokenPayload
210
+ },
211
+ get raw() {
212
+ return json
213
+ },
214
+ },
215
+ })
216
+ }
217
+
218
+ return {
219
+ type: config.type || "oauth2",
220
+ init(routes, ctx) {
221
+ routes.get("/authorize", async (c) => {
222
+ const state = crypto.randomUUID()
223
+ const pkce = config.pkce ? await generatePKCE() : undefined
224
+ await ctx.set<ProviderState>(c, "provider", 60 * 10, {
225
+ state,
226
+ redirect: getRelativeUrl(c, "./callback"),
227
+ codeVerifier: pkce?.verifier,
228
+ })
229
+ const authorization = new URL(config.endpoint.authorization)
230
+ authorization.searchParams.set("client_id", config.clientID)
231
+ authorization.searchParams.set(
232
+ "redirect_uri",
233
+ getRelativeUrl(c, "./callback"),
234
+ )
235
+ authorization.searchParams.set("response_type", "code")
236
+ authorization.searchParams.set("state", state)
237
+ authorization.searchParams.set("scope", config.scopes.join(" "))
238
+ if (pkce) {
239
+ authorization.searchParams.set("code_challenge", pkce.challenge)
240
+ authorization.searchParams.set("code_challenge_method", pkce.method)
241
+ }
242
+ for (const [key, value] of Object.entries(query)) {
243
+ authorization.searchParams.set(key, value)
244
+ }
245
+ return c.redirect(authorization.toString())
246
+ })
247
+
248
+ routes.get("/callback", async (c) => {
249
+ const provider = (await ctx.get(c, "provider")) as ProviderState
250
+ const code = c.req.query("code")
251
+ const state = c.req.query("state")
252
+ const error = c.req.query("error")
253
+
254
+ if (error)
255
+ throw new OauthError(
256
+ error.toString() as any,
257
+ c.req.query("error_description")?.toString() || "",
258
+ )
259
+ if (
260
+ !provider ||
261
+ !code ||
262
+ (provider.state && state !== provider.state)
263
+ ) {
264
+ return c.redirect(getRelativeUrl(c, "./authorize"))
265
+ }
266
+
267
+ return handleCallbackLogic(c, ctx, provider, code)
268
+ })
269
+
270
+ routes.post("/callback", async (c) => {
271
+ const provider = (await ctx.get(c, "provider")) as ProviderState
272
+
273
+ // Handle form data from POST request
274
+ const formData = await c.req.formData()
275
+ const code = formData.get("code")?.toString()
276
+ const state = formData.get("state")?.toString()
277
+ const error = formData.get("error")?.toString()
278
+
279
+ if (error)
280
+ throw new OauthError(
281
+ error as any,
282
+ formData.get("error_description")?.toString() || "",
283
+ )
284
+
285
+ if (
286
+ !provider ||
287
+ !code ||
288
+ (provider.state && state !== provider.state)
289
+ ) {
290
+ return c.redirect(getRelativeUrl(c, "./authorize"))
291
+ }
292
+
293
+ return handleCallbackLogic(c, ctx, provider, code)
294
+ })
295
+ },
296
+ }
297
+ }
@@ -0,0 +1,179 @@
1
+ /**
2
+ * Use this to connect authentication providers that support OIDC.
3
+ *
4
+ * ```ts {5-8}
5
+ * import { OidcProvider } from "@openauthjs/openauth/provider/oidc"
6
+ *
7
+ * export default issuer({
8
+ * providers: {
9
+ * oauth2: OidcProvider({
10
+ * clientId: "1234567890",
11
+ * issuer: "https://auth.myserver.com"
12
+ * })
13
+ * }
14
+ * })
15
+ * ```
16
+ *
17
+ *
18
+ * @packageDocumentation
19
+ */
20
+
21
+ import { createLocalJWKSet, JSONWebKeySet, jwtVerify } from "jose"
22
+ import { WellKnown } from "../client.js"
23
+ import { OauthError } from "../error.js"
24
+ import { Provider } from "./provider.js"
25
+ import { JWTPayload } from "hono/utils/jwt/types"
26
+ import { getRelativeUrl, lazy } from "../util.js"
27
+
28
+ export interface OidcConfig {
29
+ /**
30
+ * @internal
31
+ */
32
+ type?: string
33
+ /**
34
+ * The client ID.
35
+ *
36
+ * This is just a string to identify your app.
37
+ *
38
+ * @example
39
+ * ```ts
40
+ * {
41
+ * clientID: "my-client"
42
+ * }
43
+ * ```
44
+ */
45
+ clientID: string
46
+ /**
47
+ * The URL of your authorization server.
48
+ *
49
+ * @example
50
+ * ```ts
51
+ * {
52
+ * issuer: "https://auth.myserver.com"
53
+ * }
54
+ * ```
55
+ */
56
+ issuer: string
57
+ /**
58
+ * A list of OIDC scopes that you want to request.
59
+ *
60
+ * @example
61
+ * ```ts
62
+ * {
63
+ * scopes: ["openid", "profile", "email"]
64
+ * }
65
+ * ```
66
+ */
67
+ scopes?: string[]
68
+ /**
69
+ * Any additional parameters that you want to pass to the authorization endpoint.
70
+ * @example
71
+ * ```ts
72
+ * {
73
+ * query: {
74
+ * prompt: "consent"
75
+ * }
76
+ * }
77
+ * ```
78
+ */
79
+ query?: Record<string, string>
80
+ }
81
+
82
+ /**
83
+ * @internal
84
+ */
85
+ export type OidcWrappedConfig = Omit<OidcConfig, "issuer" | "name">
86
+
87
+ interface ProviderState {
88
+ state: string
89
+ nonce: string
90
+ redirect: string
91
+ }
92
+
93
+ /**
94
+ * @internal
95
+ */
96
+ export interface IdTokenResponse {
97
+ idToken: string
98
+ claims: Record<string, any>
99
+ raw: Record<string, any>
100
+ }
101
+
102
+ export function OidcProvider(
103
+ config: OidcConfig,
104
+ ): Provider<{ id: JWTPayload; clientID: string }> {
105
+ const query = config.query || {}
106
+ const scopes = config.scopes || []
107
+
108
+ const wk = lazy(() =>
109
+ fetch(config.issuer + "/.well-known/openid-configuration").then(
110
+ async (r) => {
111
+ if (!r.ok) throw new Error(await r.text())
112
+ return r.json() as Promise<WellKnown>
113
+ },
114
+ ),
115
+ )
116
+
117
+ const jwks = lazy(() =>
118
+ wk()
119
+ .then((r) => r.jwks_uri)
120
+ .then(async (uri) => {
121
+ const r = await fetch(uri)
122
+ if (!r.ok) throw new Error(await r.text())
123
+ return createLocalJWKSet((await r.json()) as JSONWebKeySet)
124
+ }),
125
+ )
126
+
127
+ return {
128
+ type: config.type || "oidc",
129
+ init(routes, ctx) {
130
+ routes.get("/authorize", async (c) => {
131
+ const provider: ProviderState = {
132
+ state: crypto.randomUUID(),
133
+ nonce: crypto.randomUUID(),
134
+ redirect: getRelativeUrl(c, "./callback"),
135
+ }
136
+ await ctx.set(c, "provider", 60 * 10, provider)
137
+ const authorization = new URL(
138
+ await wk().then((r) => r.authorization_endpoint),
139
+ )
140
+ authorization.searchParams.set("client_id", config.clientID)
141
+ authorization.searchParams.set("response_type", "id_token")
142
+ authorization.searchParams.set("response_mode", "form_post")
143
+ authorization.searchParams.set("state", provider.state)
144
+ authorization.searchParams.set("nonce", provider.nonce)
145
+ authorization.searchParams.set("redirect_uri", provider.redirect)
146
+ authorization.searchParams.set("scope", ["openid", ...scopes].join(" "))
147
+ for (const [key, value] of Object.entries(query)) {
148
+ authorization.searchParams.set(key, value)
149
+ }
150
+ return c.redirect(authorization.toString())
151
+ })
152
+
153
+ routes.post("/callback", async (c) => {
154
+ const provider = await ctx.get<ProviderState>(c, "provider")
155
+ if (!provider) return c.redirect(getRelativeUrl(c, "./authorize"))
156
+ const body = await c.req.formData()
157
+ const error = body.get("error")
158
+ if (error)
159
+ throw new OauthError(
160
+ error.toString() as any,
161
+ body.get("error_description")?.toString() || "",
162
+ )
163
+ const idToken = body.get("id_token")
164
+ if (!idToken)
165
+ throw new OauthError("invalid_request", "Missing id_token")
166
+ const result = await jwtVerify(idToken.toString(), await jwks(), {
167
+ audience: config.clientID,
168
+ })
169
+ if (result.payload.nonce !== provider.nonce) {
170
+ throw new OauthError("invalid_request", "Invalid nonce")
171
+ }
172
+ return ctx.success(c, {
173
+ id: result.payload,
174
+ clientID: config.clientID,
175
+ })
176
+ })
177
+ },
178
+ }
179
+ }