Skip to content

Commit e661dc3

Browse files
yasudacloudww24
andauthored
V4 feature/provider state (#95)
* fix: Use PKCE in Cognito provider (#82) * fix: Use PKCE in Google provider (#83) * fix: Use PKCE in OIDC provider (#86) * fix: Use PKCE in OIDC provider * fix: ensure state parameter is always appended in OIDC provider * fix: Add state param in Google provider (#90) * refactor: Refactor URL construction in Google provider * fix: Add state param in Google provider * refactor: generating random bytes * fix: Add state param in Cognito provider (#88) * refactor: Refactor URL construction in Cognito provider * fix: Add state param in Cognito provider * fix: Add state param in Azure AD provider (#89) * refactor: Refactor URL construction in Azure AD provider * fix: Ensure email property is set for Azure AD user in sign-in callback * fix: Add state param in Azure AD provider * fix: Add state param in OIDC provider (#91) * fix require --------- Co-authored-by: Takenori Nakagawa <[email protected]>
1 parent aa3a20c commit e661dc3

File tree

4 files changed

+112
-14
lines changed

4 files changed

+112
-14
lines changed

server/controllers/azuread.js

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
"use strict";
22
const axios = require("axios");
3-
const { v4 } = require("uuid");
3+
const {v4} = require("uuid");
44
const pkceChallenge = require("pkce-challenge").default;
5+
const {Buffer} = require('buffer');
56

67
const configValidation = () => {
78
const config = strapi.config.get("plugin.strapi-plugin-sso");
@@ -30,7 +31,6 @@ const OAUTH_RESPONSE_TYPE = "code";
3031

3132
async function azureAdSignIn(ctx) {
3233
const config = configValidation();
33-
const redirectUri = encodeURIComponent(config["AZUREAD_OAUTH_REDIRECT_URI"]);
3434
const endpoint = OAUTH_ENDPOINT(config["AZUREAD_TENANT_ID"]);
3535

3636
// Generate code verifier and code challenge
@@ -40,7 +40,18 @@ async function azureAdSignIn(ctx) {
4040
// Store the code verifier in the session
4141
ctx.session.codeVerifier = codeVerifier;
4242

43-
const url = `${endpoint}?client_id=${config["AZUREAD_OAUTH_CLIENT_ID"]}&redirect_uri=${redirectUri}&scope=${config["AZUREAD_SCOPE"]}&response_type=${OAUTH_RESPONSE_TYPE}&code_challenge=${codeChallenge}&code_challenge_method=S256`;
43+
const state = crypto.getRandomValues(Buffer.alloc(32)).toString('base64url');
44+
ctx.session.oidcState = state;
45+
46+
const params = new URLSearchParams();
47+
params.append('client_id', config['AZUREAD_OAUTH_CLIENT_ID']);
48+
params.append('redirect_uri', config['AZUREAD_OAUTH_REDIRECT_URI']);
49+
params.append('scope', config['AZUREAD_SCOPE']);
50+
params.append('response_type', OAUTH_RESPONSE_TYPE);
51+
params.append('code_challenge', codeChallenge);
52+
params.append('code_challenge_method', 'S256');
53+
params.append('state', state);
54+
const url = `${endpoint}?${params.toString()}`;
4455
ctx.set("Location", url);
4556
return ctx.send({}, 302);
4657
}
@@ -55,6 +66,9 @@ async function azureAdSignInCallback(ctx) {
5566
if (!ctx.query.code) {
5667
return ctx.send(oauthService.renderSignUpError(`code Not Found`));
5768
}
69+
if (!ctx.query.state || ctx.query.state !== ctx.session.oidcState) {
70+
return ctx.send(oauthService.renderSignUpError(`Invalid state`))
71+
}
5872

5973
const params = new URLSearchParams();
6074
params.append("code", ctx.query.code);
@@ -79,6 +93,10 @@ async function azureAdSignInCallback(ctx) {
7993
},
8094
});
8195

96+
if (!userResponse.data.email) {
97+
throw new Error('Email address is not set. Please set email property to the Azure AD user.');
98+
}
99+
82100
const dbUser = await userService.findOneByEmail(userResponse.data.email);
83101
let activateUser;
84102
let jwtToken;

server/controllers/cognito.js

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
'use strict';
22
const axios = require("axios");
33
const {v4} = require('uuid');
4+
const pkceChallenge = require("pkce-challenge").default;
5+
const {Buffer} = require('buffer');
46

57
const configValidation = () => {
68
const config = strapi.config.get('plugin.strapi-plugin-sso')
@@ -23,14 +25,32 @@ const OAUTH_USER_INFO_ENDPOINT = (domain, region) => {
2325
return `https://${domain}.auth.${region}.amazoncognito.com/oauth2/userInfo`
2426
}
2527
const OAUTH_GRANT_TYPE = 'authorization_code'
26-
const OAUTH_SCOPE = encodeURIComponent('openid email profile')
28+
const OAUTH_SCOPE = 'openid email profile'
2729
const OAUTH_RESPONSE_TYPE = 'code'
2830

2931
async function cognitoSignIn(ctx) {
3032
const config = configValidation()
31-
const redirectUri = encodeURIComponent(config['COGNITO_OAUTH_REDIRECT_URI'])
3233
const endpoint = OAUTH_ENDPOINT(config['COGNITO_OAUTH_DOMAIN'], config['COGNITO_OAUTH_REGION'])
33-
const url = `${endpoint}?client_id=${config['COGNITO_OAUTH_CLIENT_ID']}&redirect_uri=${redirectUri}&scope=${OAUTH_SCOPE}&response_type=${OAUTH_RESPONSE_TYPE}`
34+
35+
// Generate code verifier and code challenge
36+
const { code_verifier: codeVerifier, code_challenge: codeChallenge } =
37+
pkceChallenge();
38+
39+
// Store the code verifier in the session
40+
ctx.session.codeVerifier = codeVerifier;
41+
42+
const state = crypto.getRandomValues(Buffer.alloc(32)).toString('base64url');
43+
ctx.session.oidcState = state;
44+
45+
const params = new URLSearchParams();
46+
params.append('client_id', config['COGNITO_OAUTH_CLIENT_ID']);
47+
params.append('redirect_uri', config['COGNITO_OAUTH_REDIRECT_URI']);
48+
params.append('scope', OAUTH_SCOPE);
49+
params.append('response_type', OAUTH_RESPONSE_TYPE);
50+
params.append('code_challenge', codeChallenge);
51+
params.append('code_challenge_method', 'S256');
52+
params.append('state', state);
53+
const url = `${endpoint}?${params.toString()}`
3454
ctx.set('Location', url)
3555
return ctx.send({}, 302)
3656
}
@@ -45,6 +65,9 @@ async function cognitoSignInCallback(ctx) {
4565
if (!ctx.query.code) {
4666
return ctx.send(oauthService.renderSignUpError(`code Not Found`))
4767
}
68+
if (!ctx.query.state || ctx.query.state !== ctx.session.oidcState) {
69+
return ctx.send(oauthService.renderSignUpError(`Invalid state`))
70+
}
4871

4972
const params = new URLSearchParams();
5073
params.append('code', ctx.query.code);
@@ -53,6 +76,9 @@ async function cognitoSignInCallback(ctx) {
5376
params.append('redirect_uri', config['COGNITO_OAUTH_REDIRECT_URI']);
5477
params.append('grant_type', OAUTH_GRANT_TYPE);
5578

79+
// Include the code verifier from the session
80+
params.append("code_verifier", ctx.session.codeVerifier);
81+
5682
try {
5783
const tokenEndpoint = OAUTH_TOKEN_ENDPOINT(config['COGNITO_OAUTH_DOMAIN'], config['COGNITO_OAUTH_REGION'])
5884
const userInfoEndpoint = OAUTH_USER_INFO_ENDPOINT(config['COGNITO_OAUTH_DOMAIN'], config['COGNITO_OAUTH_REGION'])

server/controllers/google.js

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
const axios = require("axios");
22
const {v4} = require('uuid');
3+
const pkceChallenge = require("pkce-challenge").default;
4+
const {Buffer} = require('buffer');
35

46
const configValidation = () => {
57
const config = strapi.config.get('plugin.strapi-plugin-sso')
@@ -26,10 +28,28 @@ const OAUTH_SCOPE = 'https://www.googleapis.com/auth/userinfo.email https://www.
2628
*/
2729
async function googleSignIn(ctx) {
2830
const config = configValidation()
29-
const redirectUri = encodeURIComponent(config['GOOGLE_OAUTH_REDIRECT_URI'])
30-
const url = `${OAUTH_ENDPOINT}?client_id=${config['GOOGLE_OAUTH_CLIENT_ID']}&redirect_uri=${redirectUri}&scope=${OAUTH_SCOPE}&response_type=${OAUTH_RESPONSE_TYPE}`
31-
ctx.set('Location', url)
32-
return ctx.send({}, 302)
31+
32+
// Generate code verifier and code challenge
33+
const { code_verifier: codeVerifier, code_challenge: codeChallenge } =
34+
pkceChallenge();
35+
36+
// Store the code verifier in the session
37+
ctx.session.codeVerifier = codeVerifier;
38+
39+
const state = crypto.getRandomValues(Buffer.alloc(32)).toString('base64url');
40+
ctx.session.oidcState = state;
41+
42+
const params = new URLSearchParams();
43+
params.append('client_id', config['GOOGLE_OAUTH_CLIENT_ID']);
44+
params.append('redirect_uri', config['GOOGLE_OAUTH_REDIRECT_URI']);
45+
params.append('scope', OAUTH_SCOPE);
46+
params.append('response_type', OAUTH_RESPONSE_TYPE);
47+
params.append('code_challenge', codeChallenge);
48+
params.append('code_challenge_method', 'S256');
49+
params.append('state', state);
50+
const url = `${OAUTH_ENDPOINT}?${params.toString()}`;
51+
ctx.set('Location', url);
52+
return ctx.send({}, 302);
3353
}
3454

3555
/**
@@ -48,6 +68,9 @@ async function googleSignInCallback(ctx) {
4868
if (!ctx.query.code) {
4969
return ctx.send(oauthService.renderSignUpError(`code Not Found`))
5070
}
71+
if (!ctx.query.state || ctx.query.state !== ctx.session.oidcState) {
72+
return ctx.send(oauthService.renderSignUpError(`Invalid state`))
73+
}
5174

5275
const params = new URLSearchParams();
5376
params.append('code', ctx.query.code);
@@ -56,6 +79,9 @@ async function googleSignInCallback(ctx) {
5679
params.append('redirect_uri', config['GOOGLE_OAUTH_REDIRECT_URI']);
5780
params.append('grant_type', OAUTH_GRANT_TYPE);
5881

82+
// Include the code verifier from the session
83+
params.append("code_verifier", ctx.session.codeVerifier);
84+
5985
try {
6086
const response = await httpClient.post(OAUTH_TOKEN_ENDPOINT, params, {
6187
headers: {

server/controllers/oidc.js

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
const axios = require("axios");
2-
const { v4 } = require('uuid');
2+
const {v4} = require('uuid');
3+
const pkceChallenge = require("pkce-challenge").default;
4+
const {Buffer} = require('buffer');
35

46
const configValidation = () => {
57
const config = strapi.config.get('plugin.strapi-plugin-sso')
@@ -15,12 +17,32 @@ const configValidation = () => {
1517
}
1618

1719
const oidcSignIn = async (ctx) => {
18-
const { state } = ctx.query;
20+
let { state } = ctx.query;
1921
const { OIDC_CLIENT_ID, OIDC_REDIRECT_URI, OIDC_SCOPES, OIDC_AUTHORIZATION_ENDPOINT } = configValidation();
2022

21-
const authorizationUrl = `${OIDC_AUTHORIZATION_ENDPOINT}?response_type=code&client_id=${OIDC_CLIENT_ID}&redirect_uri=${OIDC_REDIRECT_URI}&scope=${OIDC_SCOPES}&state=${state}`;
23+
// Generate code verifier and code challenge
24+
const { code_verifier: codeVerifier, code_challenge: codeChallenge } =
25+
pkceChallenge();
2226

23-
ctx.redirect(authorizationUrl);
27+
// Store the code verifier in the session
28+
ctx.session.codeVerifier = codeVerifier;
29+
30+
if (!state) {
31+
state = crypto.getRandomValues(Buffer.alloc(32)).toString('base64url');
32+
}
33+
ctx.session.oidcState = state;
34+
35+
const params = new URLSearchParams();
36+
params.append('response_type', 'code');
37+
params.append('client_id', OIDC_CLIENT_ID);
38+
params.append('redirect_uri', OIDC_REDIRECT_URI);
39+
params.append('scope', OIDC_SCOPES);
40+
params.append('code_challenge', codeChallenge);
41+
params.append('code_challenge_method', 'S256');
42+
params.append('state', state);
43+
const authorizationUrl = `${OIDC_AUTHORIZATION_ENDPOINT}?${params.toString()}`;
44+
ctx.set('Location', authorizationUrl);
45+
return ctx.send({}, 302);
2446
};
2547

2648
const oidcSignInCallback = async (ctx) => {
@@ -34,6 +56,9 @@ const oidcSignInCallback = async (ctx) => {
3456
if (!ctx.query.code) {
3557
return ctx.send(oauthService.renderSignUpError(`code Not Found`))
3658
}
59+
if (!ctx.query.state || ctx.query.state !== ctx.session.oidcState) {
60+
return ctx.send(oauthService.renderSignUpError(`Invalid state`))
61+
}
3762

3863
const params = new URLSearchParams();
3964
params.append('code', ctx.query.code);
@@ -42,6 +67,9 @@ const oidcSignInCallback = async (ctx) => {
4267
params.append('redirect_uri', config['OIDC_REDIRECT_URI']);
4368
params.append('grant_type', config['OIDC_GRANT_TYPE']);
4469

70+
// Include the code verifier from the session
71+
params.append("code_verifier", ctx.session.codeVerifier);
72+
4573
try {
4674
const response = await httpClient.post(config['OIDC_TOKEN_ENDPOINT'], params, {
4775
headers: {

0 commit comments

Comments
 (0)