Skip to content

Commit 6b9f44b

Browse files
committed
separate Session#fedCM method
1 parent 5a0cd00 commit 6b9f44b

File tree

2 files changed

+67
-39
lines changed

2 files changed

+67
-39
lines changed

src/core/AuthorizationCodeGrant.ts

Lines changed: 60 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,48 @@ const buildRedirectUrl = (code, state, providerUrl) => {
99
return `${base}?code=${code}&state=${state}&iss=${encodeURIComponent(providerUrl)}`;
1010
};
1111

12-
/**
13-
* Login with the idp, using a provided `client_id` or dynamic client registration if none provided.
14-
*
15-
* @param idp
16-
* @param redirect_uri
17-
*/
18-
const redirectForLogin = async (idp: string, redirect_uri: string, client_details?: ClientDetails) => {
19-
// RFC 6749 - Section 3.1.2 - sanitize redirect_uri
20-
const redirect_uri_ = new URL(redirect_uri);
21-
const redirect_uri_sane = redirect_uri_.origin + redirect_uri_.pathname + redirect_uri_.search;
22-
// lookup openid configuration of idp
23-
const idp_origin = new URL(idp).origin;
12+
const fedCMLogin = async (clientId: string): Promise<string> => {
13+
14+
// RFC 7636 PKCE, remember code verifer
15+
const { pkce_code_verifier, pkce_code_challenge } = await getPKCEcode();
16+
sessionStorage.setItem("pkce_code_verifier", pkce_code_verifier);
17+
18+
// RFC 6749 OAuth 2.0 - CSRF token
19+
const csrf_token = window.crypto.randomUUID();
20+
sessionStorage.setItem("csrf_token", csrf_token);
21+
22+
const credential = await navigator.credentials.get({
23+
// @ts-ignore
24+
identity: {
25+
providers: [{
26+
configURL: 'any',
27+
clientId: clientId,
28+
registered: true,
29+
params: {
30+
code_challenge: pkce_code_challenge,
31+
code_challenge_method: 'S256',
32+
state: csrf_token
33+
}
34+
}]
35+
}
36+
});
37+
38+
console.log('FedCM returned', credential)
39+
40+
// @ts-ignore
41+
const fedCMissuer = new URL(credential.configURL)
42+
43+
// XXX: we ♥️ trailing slash errors
44+
sessionStorage.setItem("idp", fedCMissuer.origin + '/');
45+
46+
await lookupIdp(fedCMissuer.origin + '/', fedCMissuer.origin)
47+
48+
// XXX: figure out how to deal with state!!!
49+
// @ts-ignore
50+
return buildRedirectUrl(credential.token, csrf_token, fedCMissuer.origin + '/')
51+
}
52+
53+
const lookupIdp = async (idp: string, idp_origin: string) => {
2454
const openid_configuration =
2555
await fetch(`${idp_origin}/.well-known/openid-configuration`)
2656
.then((response) => {
@@ -48,6 +78,22 @@ const redirectForLogin = async (idp: string, redirect_uri: string, client_detail
4878
"jwks_uri",
4979
openid_configuration["jwks_uri"]
5080
);
81+
return openid_configuration
82+
}
83+
84+
/**
85+
* Login with the idp, using a provided `client_id` or dynamic client registration if none provided.
86+
*
87+
* @param idp
88+
* @param redirect_uri
89+
*/
90+
const redirectForLogin = async (idp: string, redirect_uri: string, client_details?: ClientDetails) => {
91+
// RFC 6749 - Section 3.1.2 - sanitize redirect_uri
92+
const redirect_uri_ = new URL(redirect_uri);
93+
const redirect_uri_sane = redirect_uri_.origin + redirect_uri_.pathname + redirect_uri_.search;
94+
// lookup openid configuration of idp
95+
const idp_origin = new URL(idp).origin;
96+
const openid_configuration = await lookupIdp(idp, idp_origin)
5197

5298
let client_id = client_details?.client_id;
5399
// no client_id => attempt dynamic registration
@@ -95,30 +141,7 @@ const redirectForLogin = async (idp: string, redirect_uri: string, client_detail
95141
`&state=${csrf_token}` +
96142
`&prompt=consent`; // this query parameter value MUST be present for CSS v7 to issue a refresh token ( // TODO open issue because prompting is the default behaviour but without this query param no refresh token is provided despite the "remember this client" box being checked)
97143

98-
// do FedCM dance 💃🏻
99-
// do check first!
100-
const params = Object.fromEntries(new URL(redirect_to_idp).searchParams);
101-
const credential = await navigator.credentials.get({
102-
// @ts-ignore
103-
identity: {
104-
providers: [{
105-
configURL: 'any',
106-
clientId: params.client_id,
107-
registered: true,
108-
params: {
109-
code_challenge: params.code_challenge,
110-
code_challenge_method: params.code_challenge_method,
111-
state: params.state
112-
}
113-
}]
114-
}
115-
});
116-
console.log(credential)
117-
// XXX: we ♥️ trailing slash errors
118-
// @ts-ignore
119-
const fedCMissuer = new URL(credential.configURL).origin + '/'
120-
// @ts-ignore
121-
return buildRedirectUrl(credential.token, params.state, fedCMissuer)
144+
window.location.href = redirect_to_idp;
122145
};
123146

124147
/**
@@ -331,4 +354,4 @@ const requestAccessToken = async (
331354
});
332355
};
333356

334-
export { redirectForLogin, onIncomingRedirect };
357+
export { redirectForLogin, fedCMLogin, onIncomingRedirect };

src/core/Session.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { SignJWT, decodeJwt, exportJWK } from "jose";
2-
import { redirectForLogin, onIncomingRedirect } from "./AuthorizationCodeGrant";
2+
import { redirectForLogin, fedCMLogin, onIncomingRedirect } from "./AuthorizationCodeGrant";
33
import { renewTokens } from "./RefreshTokenGrant";
44
import { SessionDatabase } from "./SessionDatabase";
55
import { DynamicRegistrationClientDetails, DereferencableIdClientDetails, SessionInformation, TokenDetails } from "./SessionInformation";
@@ -113,7 +113,12 @@ export class SessionCore extends EventTarget implements Session {
113113
}
114114

115115
async login(idp: string, redirect_uri: string) {
116-
const fedCMFakeUrl = await redirectForLogin(idp, redirect_uri, this.information.clientDetails)
116+
await redirectForLogin(idp, redirect_uri, this.information.clientDetails)
117+
}
118+
119+
async fedCM() {
120+
if (!this.information.clientDetails.client_id) throw new Error('FedCM requires Client ID URL')
121+
const fedCMFakeUrl = await fedCMLogin(this.information.clientDetails.client_id)
117122
await this.handleRedirectFromLogin(fedCMFakeUrl)
118123
}
119124

0 commit comments

Comments
 (0)