-
-
Save magicspon/93f42be4395702db8ec006e9cb6236b2 to your computer and use it in GitHub Desktop.
SSO Login to BigCommerce using a (Next) API Route
This file contains hidden or 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(setCookie, "SHOP_TOKEN") | |
let perfToken = getCookie(setCookie, "Shopper-Pref") | |
let sessionToken = getCookie(setCookie, "SHOP_SESSION_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 += `; Domain=${host?.includes(":") ? host?.slice(0, host.indexOf(":")) : host}` | |
perfToken += `; Domain=${host?.includes(":") ? host?.slice(0, host.indexOf(":")) : host}` | |
sessionToken += `; 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') | |
} | |
const shopCookie = concatHeader(res.getHeader("Set-Cookie"), shopToken) | |
const perfCookie = concatHeader(res.getHeader("Set-Cookie"), perfToken) | |
const sessionCookie = concatHeader(res.getHeader("Set-Cookie"),sessionToken) | |
response.setHeader( | |
'Set-Cookie', | |
[shopCookie, perfCookie, sessionCookie] | |
) | |
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
I'd recommend using miniOrange BigCommerce SSO solution rather than building your own due to following imp reasons: