@atproto/oauth-client 0.1.0 → 0.1.2-rc.0
Sign up to get free protection for your applications and to get access to all the features.
- package/CHANGELOG.md +23 -0
- package/README.md +165 -31
- package/dist/errors/token-invalid-error.d.ts +7 -0
- package/dist/errors/token-invalid-error.d.ts.map +1 -0
- package/dist/errors/token-invalid-error.js +16 -0
- package/dist/errors/token-invalid-error.js.map +1 -0
- package/dist/errors/token-refresh-error.d.ts +7 -0
- package/dist/errors/token-refresh-error.d.ts.map +1 -0
- package/dist/errors/token-refresh-error.js +16 -0
- package/dist/errors/token-refresh-error.js.map +1 -0
- package/dist/errors/token-revoked-error.d.ts +7 -0
- package/dist/errors/token-revoked-error.d.ts.map +1 -0
- package/dist/errors/token-revoked-error.js +16 -0
- package/dist/errors/token-revoked-error.js.map +1 -0
- package/dist/index.d.ts +9 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -1
- package/dist/index.js.map +1 -1
- package/dist/lock.d.ts +2 -1
- package/dist/lock.d.ts.map +1 -1
- package/dist/lock.js +2 -2
- package/dist/lock.js.map +1 -1
- package/dist/oauth-agent.d.ts.map +1 -1
- package/dist/oauth-agent.js +14 -9
- package/dist/oauth-agent.js.map +1 -1
- package/dist/oauth-atp-agent.d.ts +11 -0
- package/dist/oauth-atp-agent.d.ts.map +1 -0
- package/dist/oauth-atp-agent.js +52 -0
- package/dist/oauth-atp-agent.js.map +1 -0
- package/dist/oauth-client.d.ts +254 -24
- package/dist/oauth-client.d.ts.map +1 -1
- package/dist/oauth-client.js +68 -9
- package/dist/oauth-client.js.map +1 -1
- package/dist/oauth-resolver.d.ts +5 -4
- package/dist/oauth-resolver.d.ts.map +1 -1
- package/dist/oauth-resolver.js.map +1 -1
- package/dist/oauth-server-agent.d.ts.map +1 -1
- package/dist/oauth-server-agent.js +85 -29
- package/dist/oauth-server-agent.js.map +1 -1
- package/dist/runtime-implementation.d.ts +10 -5
- package/dist/runtime-implementation.d.ts.map +1 -1
- package/dist/runtime.d.ts +3 -3
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +18 -12
- package/dist/runtime.js.map +1 -1
- package/dist/session-getter.d.ts +19 -0
- package/dist/session-getter.d.ts.map +1 -1
- package/dist/session-getter.js +134 -42
- package/dist/session-getter.js.map +1 -1
- package/dist/state-store.d.ts +11 -0
- package/dist/state-store.d.ts.map +1 -0
- package/dist/state-store.js +3 -0
- package/dist/state-store.js.map +1 -0
- package/dist/types.d.ts +3 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/util.d.ts +10 -3
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +43 -23
- package/dist/util.js.map +1 -1
- package/dist/validate-client-metadata.d.ts.map +1 -1
- package/dist/validate-client-metadata.js +17 -0
- package/dist/validate-client-metadata.js.map +1 -1
- package/package.json +10 -8
- package/src/errors/token-invalid-error.ts +9 -0
- package/src/{refresh-error.ts → errors/token-refresh-error.ts} +1 -1
- package/src/errors/token-revoked-error.ts +9 -0
- package/src/index.ts +12 -1
- package/src/lock.ts +3 -4
- package/src/oauth-agent.ts +20 -9
- package/src/oauth-atp-agent.ts +49 -0
- package/src/oauth-client.ts +117 -34
- package/src/oauth-resolver.ts +4 -4
- package/src/oauth-server-agent.ts +9 -9
- package/src/runtime-implementation.ts +19 -11
- package/src/runtime.ts +13 -17
- package/src/session-getter.ts +135 -71
- package/src/state-store.ts +12 -0
- package/src/types.ts +5 -2
- package/src/util.ts +63 -32
- package/src/validate-client-metadata.ts +18 -0
package/src/oauth-client.ts
CHANGED
@@ -11,17 +11,20 @@ import {
|
|
11
11
|
HandleResolver,
|
12
12
|
} from '@atproto-labs/handle-resolver'
|
13
13
|
import { IdentityResolver } from '@atproto-labs/identity-resolver'
|
14
|
-
import { SimpleStore } from '@atproto-labs/simple-store'
|
15
14
|
import { SimpleStoreMemory } from '@atproto-labs/simple-store-memory'
|
16
15
|
import { Key, Keyset } from '@atproto/jwk'
|
17
16
|
import {
|
17
|
+
OAuthClientIdDiscoverable,
|
18
18
|
OAuthClientMetadata,
|
19
19
|
OAuthClientMetadataInput,
|
20
|
+
oauthClientMetadataSchema,
|
20
21
|
OAuthResponseMode,
|
21
22
|
} from '@atproto/oauth-types'
|
22
23
|
|
23
24
|
import { FALLBACK_ALG } from './constants.js'
|
25
|
+
import { TokenRevokedError } from './errors/token-revoked-error.js'
|
24
26
|
import { OAuthAgent } from './oauth-agent.js'
|
27
|
+
import { OAuthAtpAgent } from './oauth-atp-agent.js'
|
25
28
|
import {
|
26
29
|
AuthorizationServerMetadataCache,
|
27
30
|
OAuthAuthorizationServerMetadataResolver,
|
@@ -36,30 +39,26 @@ import { DpopNonceCache, OAuthServerAgent } from './oauth-server-agent.js'
|
|
36
39
|
import { OAuthServerFactory } from './oauth-server-factory.js'
|
37
40
|
import { RuntimeImplementation } from './runtime-implementation.js'
|
38
41
|
import { Runtime } from './runtime.js'
|
39
|
-
import {
|
42
|
+
import {
|
43
|
+
SessionEventMap,
|
44
|
+
SessionGetter,
|
45
|
+
SessionStore,
|
46
|
+
} from './session-getter.js'
|
47
|
+
import { InternalStateData, StateStore } from './state-store.js'
|
40
48
|
import { AuthorizeOptions, ClientMetadata } from './types.js'
|
49
|
+
import { CustomEventTarget } from './util.js'
|
41
50
|
import { validateClientMetadata } from './validate-client-metadata.js'
|
42
51
|
|
43
|
-
export type InternalStateData = {
|
44
|
-
iss: string
|
45
|
-
nonce: string
|
46
|
-
dpopKey: Key
|
47
|
-
verifier?: string
|
48
|
-
|
49
|
-
/**
|
50
|
-
* @note This could be parametrized to be of any type. This wasn't done for
|
51
|
-
* the sake of simplicity but could be added in a later development.
|
52
|
-
*/
|
53
|
-
appState?: string
|
54
|
-
}
|
55
|
-
|
56
|
-
export type StateStore = SimpleStore<string, InternalStateData>
|
57
|
-
|
58
52
|
// Export all types needed to construct OAuthClientOptions
|
59
53
|
export type {
|
60
54
|
AuthorizationServerMetadataCache,
|
55
|
+
DidCache,
|
61
56
|
DpopNonceCache,
|
62
57
|
Fetch,
|
58
|
+
HandleCache,
|
59
|
+
HandleResolver,
|
60
|
+
InternalStateData,
|
61
|
+
Key,
|
63
62
|
Keyset,
|
64
63
|
OAuthClientMetadata,
|
65
64
|
OAuthClientMetadataInput,
|
@@ -67,6 +66,7 @@ export type {
|
|
67
66
|
ProtectedResourceMetadataCache,
|
68
67
|
RuntimeImplementation,
|
69
68
|
SessionStore,
|
69
|
+
StateStore,
|
70
70
|
}
|
71
71
|
|
72
72
|
export type OAuthClientOptions = {
|
@@ -91,7 +91,47 @@ export type OAuthClientOptions = {
|
|
91
91
|
fetch?: Fetch
|
92
92
|
}
|
93
93
|
|
94
|
-
export
|
94
|
+
export type OAuthClientEventMap = SessionEventMap
|
95
|
+
|
96
|
+
export type OAuthClientFetchMetadataOptions = {
|
97
|
+
clientId: OAuthClientIdDiscoverable
|
98
|
+
fetch?: Fetch
|
99
|
+
signal?: AbortSignal
|
100
|
+
}
|
101
|
+
|
102
|
+
export class OAuthClient extends CustomEventTarget<OAuthClientEventMap> {
|
103
|
+
static async fetchMetadata({
|
104
|
+
clientId,
|
105
|
+
fetch = globalThis.fetch,
|
106
|
+
signal,
|
107
|
+
}: OAuthClientFetchMetadataOptions) {
|
108
|
+
signal?.throwIfAborted()
|
109
|
+
|
110
|
+
const request = new Request(clientId, {
|
111
|
+
redirect: 'error',
|
112
|
+
signal: signal,
|
113
|
+
})
|
114
|
+
const response = await fetch(request)
|
115
|
+
|
116
|
+
if (response.status !== 200) {
|
117
|
+
response.body?.cancel?.()
|
118
|
+
throw new TypeError(`Failed to fetch client metadata: ${response.status}`)
|
119
|
+
}
|
120
|
+
|
121
|
+
// https://drafts.aaronpk.com/draft-parecki-oauth-client-id-metadata-document/draft-parecki-oauth-client-id-metadata-document.html#section-4.1
|
122
|
+
const mime = response.headers.get('content-type')?.split(';')[0].trim()
|
123
|
+
if (mime !== 'application/json') {
|
124
|
+
response.body?.cancel?.()
|
125
|
+
throw new TypeError(`Invalid client metadata content type: ${mime}`)
|
126
|
+
}
|
127
|
+
|
128
|
+
const json: unknown = await response.json()
|
129
|
+
|
130
|
+
signal?.throwIfAborted()
|
131
|
+
|
132
|
+
return oauthClientMetadataSchema.parse(json)
|
133
|
+
}
|
134
|
+
|
95
135
|
// Config
|
96
136
|
readonly clientMetadata: ClientMetadata
|
97
137
|
readonly responseMode: OAuthResponseMode
|
@@ -132,6 +172,8 @@ export class OAuthClient {
|
|
132
172
|
runtimeImplementation,
|
133
173
|
keyset,
|
134
174
|
}: OAuthClientOptions) {
|
175
|
+
super()
|
176
|
+
|
135
177
|
this.keyset = keyset
|
136
178
|
? keyset instanceof Keyset
|
137
179
|
? keyset
|
@@ -177,6 +219,15 @@ export class OAuthClient {
|
|
177
219
|
this.runtime,
|
178
220
|
)
|
179
221
|
this.stateStore = stateStore
|
222
|
+
|
223
|
+
// Proxy sessionGetter events
|
224
|
+
for (const type of ['deleted', 'updated'] as const) {
|
225
|
+
this.sessionGetter.addEventListener(type, (event) => {
|
226
|
+
if (!this.dispatchCustomEvent(type, event.detail)) {
|
227
|
+
event.preventDefault()
|
228
|
+
}
|
229
|
+
})
|
230
|
+
}
|
180
231
|
}
|
181
232
|
|
182
233
|
// Exposed as public API for convenience
|
@@ -194,10 +245,11 @@ export class OAuthClient {
|
|
194
245
|
return this.identityResolver.handleResolver
|
195
246
|
}
|
196
247
|
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
248
|
+
get jwks() {
|
249
|
+
return this.keyset?.publicJwks ?? ({ keys: [] as const } as const)
|
250
|
+
}
|
251
|
+
|
252
|
+
async authorize(input: string, options?: AuthorizeOptions): Promise<URL> {
|
201
253
|
const redirectUri =
|
202
254
|
options?.redirect_uri ?? this.clientMetadata.redirect_uris[0]
|
203
255
|
if (!this.clientMetadata.redirect_uris.includes(redirectUri)) {
|
@@ -239,7 +291,9 @@ export class OAuthClient {
|
|
239
291
|
code_challenge_method: pkce?.method,
|
240
292
|
nonce,
|
241
293
|
state,
|
242
|
-
login_hint: identity
|
294
|
+
login_hint: identity
|
295
|
+
? input // If input is a handle or a DID, use it as a login_hint
|
296
|
+
: undefined,
|
243
297
|
response_mode: this.responseMode,
|
244
298
|
response_type:
|
245
299
|
// Negotiate by using the order in the client metadata
|
@@ -297,8 +351,24 @@ export class OAuthClient {
|
|
297
351
|
)
|
298
352
|
}
|
299
353
|
|
354
|
+
/**
|
355
|
+
* This method allows the client to proactively revoke the request_uri it
|
356
|
+
* created through PAR.
|
357
|
+
*/
|
358
|
+
async abortRequest(authorizeUrl: URL) {
|
359
|
+
const requestUri = authorizeUrl.searchParams.get('request_uri')
|
360
|
+
if (!requestUri) return
|
361
|
+
|
362
|
+
// @NOTE This is not implemented here because, 1) the request server should
|
363
|
+
// invalidate the request_uri after some delay anyways, and 2) I am not sure
|
364
|
+
// that the revocation endpoint is even supposed to support this (and I
|
365
|
+
// don't want to spend the time checking now).
|
366
|
+
|
367
|
+
// @TODO investigate actual necessity & feasibility of this feature
|
368
|
+
}
|
369
|
+
|
300
370
|
async callback(params: URLSearchParams): Promise<{
|
301
|
-
agent:
|
371
|
+
agent: OAuthAtpAgent
|
302
372
|
state: string | null
|
303
373
|
}> {
|
304
374
|
const responseJwt = params.get('response')
|
@@ -409,7 +479,7 @@ export class OAuthClient {
|
|
409
479
|
*
|
410
480
|
* @param refresh See {@link SessionGetter.getSession}
|
411
481
|
*/
|
412
|
-
async restore(sub: string, refresh?: boolean): Promise<
|
482
|
+
async restore(sub: string, refresh?: boolean): Promise<OAuthAtpAgent> {
|
413
483
|
const { dpopKey, tokenSet } = await this.sessionGetter.getSession(
|
414
484
|
sub,
|
415
485
|
refresh,
|
@@ -424,17 +494,30 @@ export class OAuthClient {
|
|
424
494
|
}
|
425
495
|
|
426
496
|
async revoke(sub: string) {
|
427
|
-
const { dpopKey, tokenSet } = await this.sessionGetter.
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
const server = await this.serverFactory.fromIssuer(tokenSet.iss, dpopKey)
|
497
|
+
const { dpopKey, tokenSet } = await this.sessionGetter.getSession(
|
498
|
+
sub,
|
499
|
+
false,
|
500
|
+
)
|
432
501
|
|
433
|
-
await
|
434
|
-
|
502
|
+
// NOT using `;(await this.restore(sub, false)).signOut()` because we want
|
503
|
+
// the tokens to be deleted even if it was not possible to fetch the issuer
|
504
|
+
// data.
|
505
|
+
try {
|
506
|
+
const server = await this.serverFactory.fromIssuer(tokenSet.iss, dpopKey)
|
507
|
+
await server.revoke(tokenSet.access_token)
|
508
|
+
} finally {
|
509
|
+
await this.sessionGetter.delStored(sub, new TokenRevokedError(sub))
|
510
|
+
}
|
435
511
|
}
|
436
512
|
|
437
|
-
createAgent(server: OAuthServerAgent, sub: string):
|
438
|
-
|
513
|
+
createAgent(server: OAuthServerAgent, sub: string): OAuthAtpAgent {
|
514
|
+
const oauthAgent = new OAuthAgent(
|
515
|
+
server,
|
516
|
+
sub,
|
517
|
+
this.sessionGetter,
|
518
|
+
this.fetch,
|
519
|
+
)
|
520
|
+
|
521
|
+
return new OAuthAtpAgent(oauthAgent)
|
439
522
|
}
|
440
523
|
}
|
package/src/oauth-resolver.ts
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
import {
|
2
|
-
|
2
|
+
ResolveIdentityOptions,
|
3
3
|
IdentityResolver,
|
4
4
|
ResolvedIdentity,
|
5
5
|
} from '@atproto-labs/identity-resolver'
|
@@ -13,7 +13,7 @@ import {
|
|
13
13
|
import { OAuthProtectedResourceMetadataResolver } from './oauth-protected-resource-metadata-resolver.js'
|
14
14
|
|
15
15
|
export type { GetCachedOptions }
|
16
|
-
export type
|
16
|
+
export type ResolveOAuthOptions = GetCachedOptions & ResolveIdentityOptions
|
17
17
|
|
18
18
|
export class OAuthResolver {
|
19
19
|
constructor(
|
@@ -24,7 +24,7 @@ export class OAuthResolver {
|
|
24
24
|
|
25
25
|
public async resolveIdentity(
|
26
26
|
input: string,
|
27
|
-
options?:
|
27
|
+
options?: ResolveIdentityOptions,
|
28
28
|
): Promise<ResolvedIdentity> {
|
29
29
|
try {
|
30
30
|
return await this.identityResolver.resolve(input, options)
|
@@ -93,7 +93,7 @@ export class OAuthResolver {
|
|
93
93
|
|
94
94
|
public async resolve(
|
95
95
|
input: string,
|
96
|
-
options?:
|
96
|
+
options?: ResolveOAuthOptions,
|
97
97
|
): Promise<{
|
98
98
|
identity: ResolvedIdentity
|
99
99
|
metadata: OAuthAuthorizationServerMetadata
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import { Fetch, Json,
|
1
|
+
import { Fetch, Json, bindFetch, fetchJsonProcessor } from '@atproto-labs/fetch'
|
2
2
|
import { SimpleStore } from '@atproto-labs/simple-store'
|
3
3
|
import { Key, Keyset, SignedJwt } from '@atproto/jwk'
|
4
4
|
import {
|
@@ -14,13 +14,13 @@ import {
|
|
14
14
|
} from '@atproto/oauth-types'
|
15
15
|
|
16
16
|
import { FALLBACK_ALG } from './constants.js'
|
17
|
+
import { TokenRefreshError } from './errors/token-refresh-error.js'
|
17
18
|
import { dpopFetchWrapper } from './fetch-dpop.js'
|
18
19
|
import { OAuthResolver } from './oauth-resolver.js'
|
19
20
|
import { OAuthResponseError } from './oauth-response-error.js'
|
20
|
-
import { RefreshError } from './refresh-error.js'
|
21
21
|
import { Runtime } from './runtime.js'
|
22
22
|
import { ClientMetadata } from './types.js'
|
23
|
-
import {
|
23
|
+
import { timeoutSignal } from './util.js'
|
24
24
|
|
25
25
|
export type TokenSet = {
|
26
26
|
iss: string
|
@@ -89,7 +89,7 @@ export class OAuthServerAgent {
|
|
89
89
|
|
90
90
|
async refresh(tokenSet: TokenSet): Promise<TokenSet> {
|
91
91
|
if (!tokenSet.refresh_token) {
|
92
|
-
throw new
|
92
|
+
throw new TokenRefreshError(tokenSet.sub, 'No refresh token available')
|
93
93
|
}
|
94
94
|
|
95
95
|
const tokenResponse = await this.request('token', {
|
@@ -99,13 +99,13 @@ export class OAuthServerAgent {
|
|
99
99
|
|
100
100
|
try {
|
101
101
|
if (tokenSet.sub !== tokenResponse.sub) {
|
102
|
-
throw new
|
102
|
+
throw new TokenRefreshError(
|
103
103
|
tokenSet.sub,
|
104
104
|
`Unexpected "sub" in token response (${tokenResponse.sub})`,
|
105
105
|
)
|
106
106
|
}
|
107
107
|
if (tokenSet.iss !== this.serverMetadata.issuer) {
|
108
|
-
throw new
|
108
|
+
throw new TokenRefreshError(tokenSet.sub, 'Issuer mismatch')
|
109
109
|
}
|
110
110
|
|
111
111
|
return this.processTokenResponse(tokenResponse)
|
@@ -132,9 +132,9 @@ export class OAuthServerAgent {
|
|
132
132
|
if (!sub) throw new TypeError(`Missing "sub" in token response`)
|
133
133
|
|
134
134
|
// @TODO (?) make timeout configurable
|
135
|
-
|
136
|
-
|
137
|
-
)
|
135
|
+
using signal = timeoutSignal(10e3)
|
136
|
+
|
137
|
+
const resolved = await this.oauthResolver.resolve(sub, { signal })
|
138
138
|
|
139
139
|
if (resolved.metadata.issuer !== this.serverMetadata.issuer) {
|
140
140
|
// Best case scenario; the user switched PDS. Worst case scenario; a bad
|
@@ -1,17 +1,25 @@
|
|
1
1
|
import { Key } from '@atproto/jwk'
|
2
|
-
|
3
|
-
export type DigestAlgorithm = {
|
4
|
-
name: 'sha256' | 'sha384' | 'sha512'
|
5
|
-
}
|
2
|
+
import { Awaitable } from './util.js'
|
6
3
|
|
7
4
|
export type { Key }
|
5
|
+
export type RuntimeKeyFactory = (algs: string[]) => Key | PromiseLike<Key>
|
6
|
+
|
7
|
+
export type RuntimeRandomValues = (length: number) => Awaitable<Uint8Array>
|
8
|
+
|
9
|
+
export type DigestAlgorithm = { name: 'sha256' | 'sha384' | 'sha512' }
|
10
|
+
export type RuntimeDigest = (
|
11
|
+
data: Uint8Array,
|
12
|
+
alg: DigestAlgorithm,
|
13
|
+
) => Awaitable<Uint8Array>
|
14
|
+
|
15
|
+
export type RuntimeLock = <T>(
|
16
|
+
name: string,
|
17
|
+
fn: () => Awaitable<T>,
|
18
|
+
) => Awaitable<T>
|
8
19
|
|
9
20
|
export interface RuntimeImplementation {
|
10
|
-
createKey
|
11
|
-
getRandomValues:
|
12
|
-
digest:
|
13
|
-
|
14
|
-
algorithm: DigestAlgorithm,
|
15
|
-
) => Uint8Array | PromiseLike<Uint8Array>
|
16
|
-
requestLock?: <T>(name: string, fn: () => T | PromiseLike<T>) => Promise<T>
|
21
|
+
createKey: RuntimeKeyFactory
|
22
|
+
getRandomValues: RuntimeRandomValues
|
23
|
+
digest: RuntimeDigest
|
24
|
+
requestLock?: RuntimeLock
|
17
25
|
}
|
package/src/runtime.ts
CHANGED
@@ -5,10 +5,22 @@ import { requestLocalLock } from './lock.js'
|
|
5
5
|
import {
|
6
6
|
DigestAlgorithm,
|
7
7
|
RuntimeImplementation,
|
8
|
+
RuntimeLock,
|
8
9
|
} from './runtime-implementation.js'
|
9
10
|
|
10
11
|
export class Runtime {
|
11
|
-
|
12
|
+
readonly hasImplementationLock: boolean
|
13
|
+
readonly usingLock: RuntimeLock
|
14
|
+
|
15
|
+
constructor(protected implementation: RuntimeImplementation) {
|
16
|
+
const { requestLock } = implementation
|
17
|
+
|
18
|
+
this.hasImplementationLock = requestLock != null
|
19
|
+
this.usingLock =
|
20
|
+
requestLock?.bind(implementation) ||
|
21
|
+
// Falling back to a local lock
|
22
|
+
requestLocalLock
|
23
|
+
}
|
12
24
|
|
13
25
|
public async generateKey(algs: string[]): Promise<Key> {
|
14
26
|
const algsSorted = Array.from(algs).sort(compareAlgos)
|
@@ -26,22 +38,6 @@ export class Runtime {
|
|
26
38
|
return base64url.baseEncode(bytes)
|
27
39
|
}
|
28
40
|
|
29
|
-
get hasLock() {
|
30
|
-
return !!this.implementation.requestLock
|
31
|
-
}
|
32
|
-
|
33
|
-
public async withLock<T>(
|
34
|
-
name: string,
|
35
|
-
fn: () => T | PromiseLike<T>,
|
36
|
-
): Promise<T> {
|
37
|
-
if (this.implementation.requestLock) {
|
38
|
-
return this.implementation.requestLock(name, fn)
|
39
|
-
} else {
|
40
|
-
// Falling back to a local lock
|
41
|
-
return requestLocalLock(name, fn)
|
42
|
-
}
|
43
|
-
}
|
44
|
-
|
45
41
|
public async validateIdTokenClaims(
|
46
42
|
token: string,
|
47
43
|
state: string,
|