-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #71 from commercelayer/jwt-bearer
Add support to JWT Bearer
- Loading branch information
Showing
12 changed files
with
426 additions
and
37 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
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
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
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,26 @@ | ||
import jwt from 'jsonwebtoken' | ||
import { createAssertion } from './jwtEncode.js' | ||
|
||
describe('createAssertion', () => { | ||
it('should be able to create a JWT assertion.', async () => { | ||
const payload = { | ||
'https://commercelayer.io/claims': { | ||
owner: { | ||
type: 'User', | ||
id: '1234' | ||
}, | ||
custom_claim: { | ||
name: 'John' | ||
} | ||
} | ||
} as const | ||
|
||
const jsonwebtokenAssertion = jwt.sign(payload, 'cl', { | ||
algorithm: 'HS512' | ||
}) | ||
|
||
const assertion = await createAssertion({ payload }) | ||
|
||
expect(assertion).toStrictEqual(jsonwebtokenAssertion) | ||
}) | ||
}) |
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,94 @@ | ||
import { encodeBase64URLSafe } from '#utils/base64.js' | ||
|
||
interface Owner { | ||
type: 'User' | 'Customer' | ||
id: string | ||
} | ||
|
||
/** | ||
* Create a JWT assertion as the first step of the [JWT bearer token authorization grant flow](https://docs.commercelayer.io/core/authentication/jwt-bearer). | ||
* | ||
* The JWT assertion is a digitally signed JSON object containing information | ||
* about the client and the user on whose behalf the access token is being requested. | ||
* | ||
* This JWT assertion can include information such as the issuer (typically the client), | ||
* the owner (the user on whose behalf the request is made), and any other relevant claims. | ||
* | ||
* @example | ||
* ```ts | ||
* const assertion = await createAssertion({ | ||
* payload: { | ||
* 'https://commercelayer.io/claims': { | ||
* owner: { | ||
* type: 'Customer', | ||
* id: '4tepftJsT2' | ||
* }, | ||
* custom_claim: { | ||
* customer: { | ||
* first_name: 'John', | ||
* last_name: 'Doe' | ||
* } | ||
* } | ||
* } | ||
* } | ||
* }) | ||
* ``` | ||
*/ | ||
export async function createAssertion({ payload }: Assertion): Promise<string> { | ||
return await jwtEncode(payload, 'cl') | ||
} | ||
|
||
interface Assertion { | ||
/** Assertion payload. */ | ||
payload: { | ||
'https://commercelayer.io/claims': { | ||
/** The customer or user you want to make the calls on behalf of. */ | ||
owner: Owner | ||
/** Any other information (key/value pairs) you want to enrich the token with. */ | ||
custom_claim?: Record<string, unknown> | ||
} | ||
} | ||
} | ||
|
||
async function jwtEncode( | ||
payload: Record<string, unknown>, | ||
secret: string | ||
): Promise<string> { | ||
const header = { alg: 'HS512', typ: 'JWT' } | ||
|
||
const encodedHeader = encodeBase64URLSafe(JSON.stringify(header)) | ||
|
||
const encodedPayload = encodeBase64URLSafe( | ||
JSON.stringify({ | ||
...payload, | ||
iat: Math.floor(new Date().getTime() / 1000) | ||
}) | ||
) | ||
|
||
const unsignedToken = `${encodedHeader}.${encodedPayload}` | ||
|
||
const signature = await createSignature(unsignedToken, secret) | ||
|
||
return `${unsignedToken}.${signature}` | ||
} | ||
|
||
async function createSignature(data: string, secret: string): Promise<string> { | ||
const enc = new TextEncoder() | ||
const algorithm = { name: 'HMAC', hash: 'SHA-512' } | ||
|
||
const key = await crypto.subtle.importKey( | ||
'raw', | ||
enc.encode(secret), | ||
algorithm, | ||
false, | ||
['sign', 'verify'] | ||
) | ||
|
||
const signature = await crypto.subtle.sign( | ||
algorithm.name, | ||
key, | ||
enc.encode(data) | ||
) | ||
|
||
return encodeBase64URLSafe(String.fromCharCode(...new Uint8Array(signature))) | ||
} |
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,30 @@ | ||
import type { TBaseOptions } from '#types/base.js' | ||
import type { TPasswordReturn } from './password.js' | ||
|
||
/** | ||
* Commerce Layer, through OAuth2, provides the support of token exchange in the on-behalf-of (delegation) scenario which allows, | ||
* for example, to make calls on behalf of a user and get an access token of the requesting user without direct user interaction. | ||
* Sales channels and webapps can accomplish it by leveraging the JWT Bearer flow, | ||
* which allows a client application to obtain an access token using a JSON Web Token (JWT) assertion. | ||
* @see https://docs.commercelayer.io/core/authentication/jwt-bearer | ||
*/ | ||
export interface TJwtBearerOptions extends TBaseOptions { | ||
/** Your application's client secret. */ | ||
clientSecret: string | ||
/** | ||
* A single JSON Web Token ([learn more](https://docs.commercelayer.io/core/authentication/jwt-bearer#creating-the-jwt-assertion)). | ||
* Max size is 4KB. | ||
* | ||
* **You can use the `createAssertion` helper method**. | ||
* | ||
* @example | ||
* ```ts | ||
* import { createAssertion } from '@commercelayer/js-auth' | ||
* ``` | ||
*/ | ||
assertion: string | ||
} | ||
|
||
export interface TJwtBearerReturn extends Omit<TPasswordReturn, 'ownerType'> { | ||
ownerType: 'user' | 'customer' | ||
} |
Oops, something went wrong.