From bc0abd6b3835d19bc2335e5a9aa77a062e0f0759 Mon Sep 17 00:00:00 2001 From: Yuqiu Wang Date: Tue, 12 Sep 2023 16:36:30 -0500 Subject: [PATCH 1/2] add OIDC_CODE_CHALLENGE --- server/index.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/server/index.js b/server/index.js index 050a1ce..b60cdab 100644 --- a/server/index.js +++ b/server/index.js @@ -13,6 +13,7 @@ const OIDC_CLIENT_ID = process.env.OIDC_CLIENT_ID; const OIDC_SECRET = process.env.OIDC_SECRET; const OIDC_URL = process.env.OIDC_URL; const OIDC_SCOPES = process.env.OIDC_SCOPES || 'openid email'; +const OIDC_CODE_CHALLENGE = process.env.OIDC_CODE_CHALLENGE || ''; const OIDC_CODE_CHALLENGE_METHOD = process.env.OIDC_CODE_CHALLENGE_METHOD || 'plain'; const OIDC_METADATA = JSON.parse(process.env.OIDC_METADATA || '{}'); const clientMetadata = Object.assign({client_id: OIDC_CLIENT_ID, client_secret: OIDC_SECRET}, OIDC_METADATA); @@ -131,7 +132,18 @@ async function getOidcEndpoint() { if (!OIDC_URL) return; const provider = await getOidcProvider(); - return provider.authorizationUrl({scope: OIDC_SCOPES, code_challenge_method: OIDC_CODE_CHALLENGE_METHOD}); + const isUsePKCE = OIDC_CODE_CHALLENGE !== "" && OIDC_CODE_CHALLENGE_METHOD === "S256" + let authParams = { + scope: OIDC_SCOPES, + } + if (isUsePKCE) { + authParams = { + ...authParams, + code_challenge: OIDC_CODE_CHALLENGE, + code_challenge_method: OIDC_CODE_CHALLENGE_METHOD + } + } + return provider.authorizationUrl(authParams); } async function oidcAuthenticate(code, redirectUri) { From 8c42ba901685bea2a497e74b18c2eee443fe4a79 Mon Sep 17 00:00:00 2001 From: Yuqiu Wang Date: Tue, 12 Sep 2023 17:04:02 -0500 Subject: [PATCH 2/2] Actually implement PKCE --- server/authUtils.js | 42 ++++++++++++++++++++++++++++++++++++++++++ server/index.js | 23 ++++++++++++++++------- 2 files changed, 58 insertions(+), 7 deletions(-) create mode 100644 server/authUtils.js diff --git a/server/authUtils.js b/server/authUtils.js new file mode 100644 index 0000000..9dc04ff --- /dev/null +++ b/server/authUtils.js @@ -0,0 +1,42 @@ +/* + Code copied from https://stackoverflow.com/questions/63309409/creating-a-code-verifier-and-challenge-for-pkce-auth-on-spotify-api-in-reactjs + */ + +// GENERATING CODE VERIFIER +function dec2hex(dec) { + return ("0" + dec.toString(16)).substr(-2); +} + +function generateCodeVerifier() { + var array = new Uint32Array(56 / 2); + window.crypto.getRandomValues(array); + return Array.from(array, dec2hex).join(""); +} + +// Generate code challenge from code verifier + +function sha256(plain) { + // returns promise ArrayBuffer + const encoder = new TextEncoder(); + const data = encoder.encode(plain); + return window.crypto.subtle.digest("SHA-256", data); +} + +function base64urlencode(a) { + var str = ""; + var bytes = new Uint8Array(a); + var len = bytes.byteLength; + for (var i = 0; i < len; i++) { + str += String.fromCharCode(bytes[i]); + } + return btoa(str) + .replace(/\+/g, "-") + .replace(/\//g, "_") + .replace(/=+$/, ""); +} + +async function generateCodeChallengeFromVerifier(v) { + var hashed = await sha256(v); + var base64encoded = base64urlencode(hashed); + return base64encoded; +} \ No newline at end of file diff --git a/server/index.js b/server/index.js index b60cdab..0c4a662 100644 --- a/server/index.js +++ b/server/index.js @@ -13,11 +13,14 @@ const OIDC_CLIENT_ID = process.env.OIDC_CLIENT_ID; const OIDC_SECRET = process.env.OIDC_SECRET; const OIDC_URL = process.env.OIDC_URL; const OIDC_SCOPES = process.env.OIDC_SCOPES || 'openid email'; -const OIDC_CODE_CHALLENGE = process.env.OIDC_CODE_CHALLENGE || ''; -const OIDC_CODE_CHALLENGE_METHOD = process.env.OIDC_CODE_CHALLENGE_METHOD || 'plain'; +const OIDC_USE_PKCE = process.env.OIDC_USE_PKCE === "true" || false; const OIDC_METADATA = JSON.parse(process.env.OIDC_METADATA || '{}'); const clientMetadata = Object.assign({client_id: OIDC_CLIENT_ID, client_secret: OIDC_SECRET}, OIDC_METADATA); +const codeVerifier = generateCodeVerifier() +const codeChallenge = generateCodeChallengeFromVerifier(codeVerifier) + + console.log('OIDC_URL: ', OIDC_URL || 'None'); process.on('uncaughtException', err => console.error('Uncaught exception', err)); @@ -132,15 +135,14 @@ async function getOidcEndpoint() { if (!OIDC_URL) return; const provider = await getOidcProvider(); - const isUsePKCE = OIDC_CODE_CHALLENGE !== "" && OIDC_CODE_CHALLENGE_METHOD === "S256" let authParams = { scope: OIDC_SCOPES, } - if (isUsePKCE) { + if (OIDC_USE_PKCE) { authParams = { ...authParams, - code_challenge: OIDC_CODE_CHALLENGE, - code_challenge_method: OIDC_CODE_CHALLENGE_METHOD + code_challenge: codeChallenge, + code_challenge_method: "S256" } } return provider.authorizationUrl(authParams); @@ -148,7 +150,14 @@ async function getOidcEndpoint() { async function oidcAuthenticate(code, redirectUri) { const provider = await getOidcProvider(); - const tokenSet = await provider.callback(redirectUri, {code}, {}); + let authCheckParams = {} + if (OIDC_USE_PKCE) { + authCheckParams = { + ...authCheckParams, + code_verifier: codeVerifier + } + } + const tokenSet = await provider.callback(redirectUri, {code}, authCheckParams); return tokenSet.id_token; }