Last active
January 3, 2024 14:06
-
-
Save jorgemasta/c709b63501344970026f3a3e7646cc77 to your computer and use it in GitHub Desktop.
SSO Login to BigCommerce using a (Next) API Route
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import type { NextApiHandler, NextApiRequest, NextApiResponse } from 'next' | |
import jwt from 'jsonwebtoken'; | |
import {v4 as uuidv4} from 'uuid'; | |
import concatHeader from '../utils/concat-cookie' | |
import getConfig from '../utils/get-config' | |
function getSsoLoginUrl(customerId: number, storeHash: string, storeUrl: string, clientId: string, clientSecret: string) { | |
const dateCreated = Math.round((new Date()). getTime() / 1000); | |
const payload = { | |
"iss": clientId, | |
"iat": dateCreated, | |
"jti": uuidv4(), | |
"operation": "customer_login", | |
"store_hash": storeHash, | |
"customer_id": customerId, | |
"redirect_to": "/" | |
} | |
let token = jwt.sign(payload, clientSecret, { algorithm:'HS256' }); | |
return `${storeUrl}/login/token/${token}`; | |
}; | |
function getCookie(header: string | null, cookieKey: string) { | |
if (!header) return null | |
const cookies : string[] = header.split(/, (?=[^;]+=[^;]+;)/) | |
return cookies.find(cookie => cookie.startsWith(`${cookieKey}=`)) | |
} | |
function externalAuthProvider() { | |
// Auth the user against an external auth provider | |
// It should return a BigCommerce customer ID | |
return { customerId: 157 } | |
} | |
const ssoLoginApi : NextApiHandler = async (request, response) => { | |
const config = getConfig() | |
const { customerId } = externalAuthProvider() | |
const ssoLoginUrl = getSsoLoginUrl(customerId, config.storeHash, config.storeUrl, config.clientId, config.clientSecret) | |
const { headers } = await fetch(ssoLoginUrl, { | |
redirect: "manual" // Important! | |
}) | |
// Set-Cookie returns several cookies, we only want SHOP_TOKEN | |
let shopToken = getCookie(headers.get('Set-Cookie'), 'SHOP_TOKEN') | |
if (shopToken && typeof shopToken === 'string') { | |
const { host } = request.headers | |
// OPTIONAL: Set the cookie at TLD to make it accessible on subdomains (embedded checkout) | |
shopToken = shopToken + `; Domain=${host?.includes(':') ? host?.slice(0, host.indexOf(':')) : host}` | |
// In development, don't set a secure shopToken or the browser will ignore it | |
if (process.env.NODE_ENV !== 'production') { | |
shopToken = shopToken.replace(/; Secure/gi, '') | |
// console.log('shopToken_replaced', shopToken) | |
// SameSite=none can't be set unless the shopToken is Secure | |
// bc seems to sometimes send back SameSite=None rather than none so make | |
// this case insensitive | |
shopToken = shopToken.replace(/; SameSite=none/gi, '; SameSite=lax') | |
} | |
response.setHeader( | |
'Set-Cookie', | |
concatHeader(response.getHeader('Set-Cookie'), shopToken)! | |
) | |
return response.status(200).json({ result: "success" }) | |
} | |
return response.status(500).json({ error: "Invalid authentication" }) | |
} | |
export default ssoLoginApi |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment