Skip to content

Instantly share code, notes, and snippets.

@jorgemasta
Last active January 3, 2024 14:06
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save jorgemasta/c709b63501344970026f3a3e7646cc77 to your computer and use it in GitHub Desktop.
Save jorgemasta/c709b63501344970026f3a3e7646cc77 to your computer and use it in GitHub Desktop.
SSO Login to BigCommerce using a (Next) API Route
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