Skip to content

Commit

Permalink
chore: add documentation about the 'createAssertion'
Browse files Browse the repository at this point in the history
  • Loading branch information
marcomontalbano committed Mar 26, 2024
1 parent 73beb10 commit 7d0e266
Show file tree
Hide file tree
Showing 6 changed files with 178 additions and 38 deletions.
45 changes: 45 additions & 0 deletions packages/js-auth/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ A JavaScript Library wrapper that helps you use the Commerce Layer API for [Auth
- [Integration application with client credentials flow](#integration-client-credentials)
- [Webapp application with authorization code flow](#webapp-authorization-code)
- [Provisioning application](#provisioning)
- [JWT bearer](#jwt-bearer)
- [Utilities](#utilities)
- [Decode an access token](#decode-an-access-token)
- [Contributors guide](#contributors-guide)
Expand Down Expand Up @@ -205,6 +206,50 @@ console.log('My access token: ', auth.accessToken)
console.log('Expiration date: ', auth.expires)
```
### JWT bearer
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](https://docs.commercelayer.io/core/authentication/jwt-bearer),
which allows a client application to obtain an access token using a JSON Web Token (JWT) [_assertion_](https://docs.commercelayer.io/core/authentication/jwt-bearer#creating-the-jwt-assertion).
You can use this code to create an _assertion_:
```ts
const assertion = await createAssertion({
payload: {
'https://commercelayer.io/claims': {
owner: {
type: 'Customer',
id: '4tepftJsT2'
},
custom_claim: {
customer: {
first_name: 'John',
last_name: 'Doe'
}
}
}
}
})
```
You can now get an access token using the `urn:ietf:params:oauth:grant-type:jwt-bearer` grant type:
```ts
import { authenticate } from '@commercelayer/js-auth'
const auth = await authenticate('urn:ietf:params:oauth:grant-type:jwt-bearer', {
clientId: 'your-client-id',
clientSecret: 'your-client-secret',
scope: 'market:code:europe',
assertion
})
console.log('My access token: ', auth.accessToken)
console.log('Expiration date: ', auth.expires)
```
## Utilities
### Decode an access token
Expand Down
4 changes: 2 additions & 2 deletions packages/js-auth/src/jwtEncode.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import jwt from 'jsonwebtoken'
import { createAssertion } from './jwtEncode.js'

describe('createAssertion', () => {
it('should be able to parse a "dashboard" access token.', async () => {
it('should be able to create a JWT assertion.', async () => {
const payload = {
'https://commercelayer.io/claims': {
owner: {
Expand All @@ -19,7 +19,7 @@ describe('createAssertion', () => {
algorithm: 'HS512'
})

const assertion = await createAssertion(payload)
const assertion = await createAssertion({ payload })

expect(assertion).toStrictEqual(jsonwebtokenAssertion)
})
Expand Down
50 changes: 42 additions & 8 deletions packages/js-auth/src/jwtEncode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,51 @@ interface Owner {
id: string
}

export async function createAssertion(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>
}
}): Promise<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
Expand Down
9 changes: 1 addition & 8 deletions packages/js-auth/src/types/jwtBearer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,7 @@ export interface TJwtBearerOptions extends TBaseOptions {
*
* @example
* ```ts
* {
* assertion: await createAssertion({
* owner: {
* type: 'Customer',
* id: 'aEwdr55W'
* }
* })
* }
* import { createAssertion } from '@commercelayer/js-auth'
* ```
*/
assertion: string
Expand Down
65 changes: 65 additions & 0 deletions packages/js-auth/src/utils/base64.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { atob, btoa, base64url } from './base64.js'

const stringifiedObject = JSON.stringify({
customer: {
first_name: 'John',
last_name: 'Doe'
}
})

describe('btoa', () => {
it('should be able to create a Base64-encoded ASCII string from a binary string.', () => {
expect(btoa('')).toEqual('')
expect(btoa('Hello, world')).toEqual('SGVsbG8sIHdvcmxk')

expect(btoa(stringifiedObject)).toEqual(
'eyJjdXN0b21lciI6eyJmaXJzdF9uYW1lIjoiSm9obiIsImxhc3RfbmFtZSI6IkRvZSJ9fQ=='
)
})

expect(
btoa(
'0\x82\x0760\x82\x06\x1E \x03\x02\x01\x02\x02\x10\tW¸\x13HxölÈÐ×\x12¨Ìµú0'
)
).toEqual('MIIHNjCCBh6gAwIBAgIQCVe4E0h49mzI0NcSqMy1+jA=')

expect(btoa('subjects?_d=1')).toEqual('c3ViamVjdHM/X2Q9MQ==')
})

describe('atob', () => {
it('should be able to decode a string of data which has been encoded using Base64 encoding.', () => {
expect(atob('')).toEqual('')
expect(atob('SGVsbG8sIHdvcmxk')).toEqual('Hello, world')

expect(
atob(
'eyJjdXN0b21lciI6eyJmaXJzdF9uYW1lIjoiSm9obiIsImxhc3RfbmFtZSI6IkRvZSJ9fQ=='
)
).toEqual(stringifiedObject)

expect(atob('MIIHNjCCBh6gAwIBAgIQCVe4E0h49mzI0NcSqMy1+jA=')).toEqual(
'0\x82\x0760\x82\x06\x1E \x03\x02\x01\x02\x02\x10\tW¸\x13HxölÈÐ×\x12¨Ìµú0'
)

expect(atob('c3ViamVjdHM/X2Q9MQ==')).toEqual('subjects?_d=1')
})
})

describe('base64url', () => {
it('should be able to create a Base64-encoded ASCII string from a binary string.', () => {
expect(base64url('')).toEqual('')
expect(base64url('Hello, world')).toEqual('SGVsbG8sIHdvcmxk')

expect(base64url(stringifiedObject)).toEqual(
'eyJjdXN0b21lciI6eyJmaXJzdF9uYW1lIjoiSm9obiIsImxhc3RfbmFtZSI6IkRvZSJ9fQ'
)

expect(
base64url(
'0\x82\x0760\x82\x06\x1E \x03\x02\x01\x02\x02\x10\tW¸\x13HxölÈÐ×\x12¨Ìµú0'
)
).toEqual('MIIHNjCCBh6gAwIBAgIQCVe4E0h49mzI0NcSqMy1-jA')

expect(base64url('subjects?_d=1')).toEqual('c3ViamVjdHM_X2Q9MQ')
})
})
43 changes: 23 additions & 20 deletions packages/js-auth/src/utils/base64.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,50 @@
/**
* The `atob()` function decodes a string of data
* which has been encoded using [Base64](https://developer.mozilla.org/en-US/docs/Glossary/Base64) encoding.
* Creates a [Base64](https://developer.mozilla.org/en-US/docs/Glossary/Base64)-encoded [ASCII](https://developer.mozilla.org/en-US/docs/Glossary/ASCII)
* string from a _binary string_ (i.e., a string in which each character in the string is treated as a byte of binary data).
*
* This method works both in Node.js and browsers.
*
* @link [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/atob)
* @param encodedData A binary string (i.e., a string in which each character in the string is treated as a byte of binary data) containing base64-encoded data.
* @returns An ASCII string containing decoded data from `encodedData`.
* @link [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/btoa)
* @param stringToEncode The binary string to encode.
* @returns An ASCII string containing the Base64 representation of `stringToEncode`.
*/
export function atob(encodedData: string): string {
export function btoa(stringToEncode: string): string {
if (typeof window !== 'undefined') {
return window.atob(encodedData)
return window.btoa(stringToEncode)
}

return Buffer.from(encodedData, 'base64').toString('binary')
return Buffer.from(stringToEncode, 'binary').toString('base64')
}

/**
* The `btoa()` method creates a [Base64](https://developer.mozilla.org/en-US/docs/Glossary/Base64)-encoded [ASCII](https://developer.mozilla.org/en-US/docs/Glossary/ASCII)
* string from a _binary string_ (i.e., a string in which each character in the string is treated as a byte of binary data).
* Decodes a string of data
* which has been encoded using [Base64](https://developer.mozilla.org/en-US/docs/Glossary/Base64) encoding.
*
* This method works both in Node.js and browsers.
*
* @link [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/btoa)
* @param stringToEncode The binary string to encode.
* @returns An ASCII string containing the Base64 representation of `stringToEncode`.
* @link [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/atob)
* @param encodedData A binary string (i.e., a string in which each character in the string is treated as a byte of binary data) containing base64-encoded data.
* @returns An ASCII string containing decoded data from `encodedData`.
*/
export function btoa(stringToEncode: string): string {
export function atob(encodedData: string): string {
if (typeof window !== 'undefined') {
return window.btoa(stringToEncode)
return window.atob(encodedData)
}

return Buffer.from(stringToEncode, 'binary').toString('base64')
return Buffer.from(encodedData, 'base64').toString('binary')
}

/**
* The "Base64 URL safe" omits the padding `=` and replaces `+/` with `-_`
* to avoid characters that might cause problems in URL path segments or query parameters.
*
* @param source
* @returns
* This is a common variant of [Base64](https://developer.mozilla.org/en-US/docs/Glossary/Base64).
* @param stringToEncode The binary string to encode.
* @returns An ASCII string containing the Base64 URL safe representation of `stringToEncode`.
*/
export function base64url(source: string): string {
export function base64url(stringToEncode: string): string {
return (
btoa(source)
btoa(stringToEncode)
// Remove padding equal characters
.replace(/=+$/, '')
// Replace characters according to base64url specifications
Expand Down

0 comments on commit 7d0e266

Please sign in to comment.