-
Notifications
You must be signed in to change notification settings - Fork 188
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add /login/token to support customer login API (#1775)
* Add /login/token to support Customer Login API * Add stub for generating Customer Login API tokens
- Loading branch information
1 parent
6eb30ac
commit 632a645
Showing
10 changed files
with
367 additions
and
170 deletions.
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@bigcommerce/catalyst-core": patch | ||
--- | ||
|
||
Add stub for generating Customer Login API tokens for SSO integrations |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@bigcommerce/catalyst-core": patch | ||
--- | ||
|
||
Add /login/token endpoint to power Customer Login API |
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
/* | ||
* This route is used to accept customer login token JWTs from the | ||
* [Customer Login API](https://developer.bigcommerce.com/docs/start/authentication/customer-login) | ||
* and log the customers in using alternative authentication methods | ||
*/ | ||
|
||
import { decodeJwt } from 'jose'; | ||
// eslint-disable-next-line @typescript-eslint/no-restricted-imports | ||
import { redirect, unstable_rethrow as rethrow } from 'next/navigation'; | ||
|
||
import { signIn } from '~/auth'; | ||
|
||
interface TokenParams { | ||
params: Promise<{ token: string }>; | ||
} | ||
|
||
export async function GET(request: Request, { params }: TokenParams) { | ||
const token = (await params).token; | ||
|
||
try { | ||
// decode token without checking signature to get redirect path | ||
// token is not checked for validity here, so it could be expired or invalid at this point | ||
// token validity and signature are checked in the signIn function | ||
const claims = decodeJwt(token); | ||
const redirectTo = | ||
typeof claims.redirect_to === 'string' ? claims.redirect_to : '/account/orders'; | ||
|
||
// sign in with token which will check validity against BigCommerce API | ||
// and redirect to redirectTo | ||
await signIn('credentials', { | ||
type: 'jwt', | ||
jwt: token, | ||
redirectTo, | ||
}); | ||
} catch (error) { | ||
rethrow(error); | ||
|
||
redirect(`/login?error=InvalidToken`); | ||
} | ||
} | ||
|
||
export const runtime = 'edge'; | ||
export const dynamic = 'force-dynamic'; |
This file was deleted.
Oops, something went wrong.
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import { randomUUID } from 'crypto'; | ||
import { SignJWT } from 'jose'; | ||
|
||
/** | ||
* Build a Customer Login API JWT which can be used in auth/index.ts to log in a customer | ||
* using the LoginWithTokenMutation, or used as a redirect to /login/token/[token] | ||
* | ||
* This is a stub intended to be used when implementing 3rd party authentication callbacks | ||
* | ||
* Requires that BIGCOMMERCE_CLIENT_SECRET and BIGCOMMERCE_CLIENT_ID are set in the environment | ||
* from a client that has the Customer Login scope enabled | ||
* | ||
* @param {number} customerId - The BigCommerce customer ID to generate the login token for | ||
* @param {number} [channelId] - Channel ID that the customer will be logged into | ||
* @param {string} [redirectTo] - Relative URL to redirect to after successful login | ||
* @param {Record<string, any>} [additionalClaims] - Optional additional claims to include in the JWT | ||
* @returns {Promise<string>} A JWT token that can be used to authenticate the customer | ||
* @throws {Error} If BIGCOMMERCE_CLIENT_SECRET is not set in environment variables | ||
* @throws {Error} If BIGCOMMERCE_CLIENT_ID is not set in environment variables | ||
*/ | ||
export const generateCustomerLoginApiJwt = async ( | ||
customerId: number, | ||
channelId: number, | ||
redirectTo: string = '/account/orders', | ||
additionalClaims?: Record<string, any>, | ||
): Promise<string> => { | ||
const clientId = process.env.BIGCOMMERCE_CLIENT_ID; | ||
const clientSecret = process.env.BIGCOMMERCE_CLIENT_SECRET; | ||
const storeHash = process.env.BIGCOMMERCE_STORE_HASH; | ||
|
||
if (!clientSecret) { | ||
throw new Error('BIGCOMMERCE_CLIENT_SECRET is not set in environment variables'); | ||
} | ||
|
||
if (!clientId) { | ||
throw new Error('BIGCOMMERCE_CLIENT_ID is not set in environment variables'); | ||
} | ||
|
||
if (!storeHash) { | ||
throw new Error('BIGCOMMERCE_STORE_HASH is not set in environment variables'); | ||
} | ||
|
||
const payload = { | ||
iss: clientId, | ||
iat: Math.floor(Date.now() / 1000), | ||
jti: randomUUID(), | ||
operation: 'customer_login', | ||
store_hash: storeHash, | ||
customer_id: Math.floor(customerId), | ||
...(channelId && { channel_id: channelId }), | ||
...(redirectTo && { redirect_to: redirectTo }), | ||
...(additionalClaims || {}), | ||
}; | ||
|
||
// Convert client secret to Uint8Array for jose library | ||
const secretKey = new TextEncoder().encode(clientSecret); | ||
|
||
// Create and sign the JWT | ||
return await new SignJWT(payload).setProtectedHeader({ alg: 'HS256', typ: 'JWT' }).sign(secretKey); | ||
}; |
Oops, something went wrong.