Access Management in brX GraphQL Service
Introduction
Every request to the brX GraphQL Service should include an access token. The brX GraphQL Service has an Access Manager component which reads and validates the access token to authorize the request (for the visitor in the store).
JSON Web Encryption (JWE) and Keystore Management
The Access Manager component delegates the authentication and authorization handling to each Commerce Backend Platform specific implementation. The brX GraphQL Service does not store any data of the visitor or the visitor's session. Once the visitor is authenticated by the Commerce Backend Platform specific implementation, the Access Manager component wraps all the session related information in an encrypted JSON object and sends it back to client as the access token to be used onward. The JSON Object is securely encrypted based on the JWE standard.
Administrators should specify the keystore entry in the .env file like the example below:
JWK_KEYSTORE={"keys":[{"kty":"<KEY_TYPE (e.g. oct)>","kid":"<KEY_ID>","k":"<KEY_VALUE>"}]}
The keystore value format is compliant to the structure defined in the cisco/node-jose library.
Also ensure that all the nodes in your clusters have the same value for the JWK_KEYSTORE property with the same identical keys sequence. On the other hand, the decryption process doesn't care about the position of the keys in the keystore.
How to Generate a JWK Keystore
As explained in the following two subsections, the keystore JSON string can be generated either (1) on the command line using the node-jose-tools library or (2) with a custom script using the cisco/node-jose library directly.
Option 1: Command Line Example to Generate a JWK Keystore
Here's an example on how to generate a JWK Keystore on the command line using the node-jose-tools library. You can run this in any folder.
$ npm install -g node-jose-tools $ jose newkey -s 256 -t oct {"kty":"oct","kid":"_tkK4V3_7vqmCWb1JOKUCRUwh_iGFe_WGIB8wDJ4lD4","k":"WzviGv-8uVE8ZPvfdh-od9Wer4nVjuqY4GdpJA5BlVo"}
Now you can copy and paste the output as the value of the JWK_KEYSTORE environment variable.
Find more advanced options and further details in https://www.npmjs.com/package/node-jose-tools.
Option 2: Custom Script Example to Generate a JWK Keystore
Alternatively, you can write a custom script to create or manage a JWK Keystore by using the cisco/node-jose library directly like the following example. In any folder, create a .js file like the following (e.g, ~/tmp/genkeystore.js):
// // Example script to generate a JWS keystore using node-jose library (https://github.com/cisco/node-jose). // // Usage: node ./genkeystore.js // var jose = require('node-jose'); // Create an empty keystore. var keystore = jose.JWK.createKeyStore(); // Generate a new key and add it to the keystore. Find more advanced options in https://github.com/cisco/node-jose. keystore.generate('oct', 256).then(function(key) { keystore.add(key); // Print out the keystore in JSON. console.log(`JWK_KEYSTORE=${JSON.stringify(keystore.toJSON(true))}`); });
This example script will import the cisco/node-jose library, create an empty keystore, put a new generated key and print out the keystore in JSON format like the following example:
$ npm install node-jose $ node ./genkeystore.js JWK_KEYSTORE={"keys":[{"kty":"oct","kid":"1-LS7ZwQjRSQaGQjk7bGQoF7FW5C6nr9rOVwxMZ6290","k":"NVt6ylQvjaizeahZ16czCQDMx8bfSKoEaFStRbf2OgI"}]}
You can copy and paste the output to your .env file.
Find more advanced options and further details in https://github.com/cisco/node-jose.
Key Rotation
In case System Administrators plan to rotate the encryption key, they can simply prepend the new key into the keys array, like the following:
JWK_KEYSTORE={"keys":[{"kty":"KEY_TYPE","kid":"NEW_KEY_ID","k":"NEW_KEY_VALUE"},{"kty":"KEY_TYPE","kid":"KEY_ID","k":"KEY_VALUE"}]}
The new key always takes the first position of the keystore, while existing keys should not be immediatily removed. This ensures:
- Newly created session data are encrypted using the new key
- Session data encrypted with an old key can be successfully decrypted
Once the changes to the keystore have been applied, the GraphQL Service nodes must be restarted.
Authentication in the brX GraphQL Service
Before sending any request to the brX GraphQL service, the client application needs to be authenticated first. The brX GraphQL Service supports a customer-based authentication mechanism, accepting customer credentials. Considering the "nature" of the store visitors, the brX GraphQL Service supports 2 types of authentication scope:
- Anonymous
- Customer Credentials
Anonymouys Authentication
Anonymous Authentication (i.e. public) provides anonymous and limited access to the brX GraphQL Service. The image below depicts the flow occurring during the anonymous authentication, starting from the client application (e.g. SPA) to the commerce backend authorization server (e.g. based on OAuth2).
In order to obtain anonymous access data, please execute the following:
curl -i \ -d '{}' \ -H 'Content-Type: application/json' \ -H 'connector: <CONNECTOR_ID>' \ https://<BRX_GRAPHQL_SERVICE_HOST>/signin
Customer Authentication
Customer Authentication scope is used when the client specifies customer credentials. The image below depicts an high-level interaction flow during customer authentication. Please note that, compared to the first diagram, customer credentials are now included as part of the sign-in request.
To obtain customer-related access data, please execute the following:
curl -i \ -d '{"username":"<USERNAME>", "password":"<PASSWORD>", "authHint": { "oldCartId": "ANONYMOUS_CART_ID", "mergeWithExistingCustomerCart": "true OR false"} }' \ -H 'Content-Type: application/json' \ -H 'connector: <CONNECTOR_ID>' \ -H 'authorization: Bearer <ANONYMOUS_ACCESS_TOKEN>' \ https://<BRX_GRAPHQL_SERVICE_HOST>/signin
Based on the commerce backend APIs, the sign-in process mostly consists of 2 steps:
- Authentication against the authorization server (e.g. OAuth2): the authorization server validates the credentials and if everything goes well returns the access data (e.g. tokens) to the brX GraphQL Service.
- Sign-in request against the Commerce Backend REST API: this enables the backend to transfer anonymous interaction data (e.g. anonymous cart previously created) to the new customer session.
Considering the steps described above additional informations are required, like:
- authorization header, containing the anonymous user access token,
- authHint property, possily containing the oldCartId and the mergeWithExistingCustomerCart options.
Authentication Hint
The sign-in operation may include additional visitor information as part of the authHint property. Those data may be needed, as example, while tracking anonymous interactions. Currently the authHint property accepts two items, the oldCartId and the mergeWithExistingCustomerCart. Let's see some possible combinations:
- If the authorization header and oldCartId is provided,
- AND mergeWithExistingCustomerCart is set to true, then the anonymous cart is merged with the latest active customer cart;
- AND mergeWithExistingCustomerCart is set to false, then the anonymous cart replaces the latest active customer cart;
- AND mergeWithExistingCustomerCart is not specified, the default value is true;
- If the oldCartId is not provided, then the anonymous cart is ignored.
Sign-out
The brX GraphQL Service provides the sign-out functionality enabling an authenticated customer to end the session with the commerce backend:
curl -i -d '{}' \ -H 'Content-Type: application/json' \ -H 'connector: <CONNECTOR_ID>' \ -H 'authorization: Bearer <ACCESS_TOKEN>' \ https://<BRX_GRAPHQL_SERVICE_HOST>/signout
If everything goes well, the response includes brand new access data, this time tied to a new anonymous interaction. If the commerce backend supports session revocation, the related commerce connector may also revoke the access token previously bound to the customer session.
Refreshing Access Token
The Commerce Backend Platform specific implementation for the Access Manager component may produce access tokens as an encrypted JSON object which contains temporary session specific data that should eventually be refreshed.
For example, if the authorization implementation is based on OAuth2, the JSON object should include at least two items:
- Access token (e.g. related to the visitor session)
- Refresh token (e.g. related to the visitor session)
Both items can be temporary. For example, once the access token has expired, any subsequent requests using the same access token throw errors. To update the access token provided by the brX GraphQL Service, the client may send a "refresh" request:
curl -i -d '{"type":"refresh"}' \ -H 'Content-Type: application/json' \ -H 'connector: <CONNECTOR_ID>' \ -H 'authorization: Bearer <ACCESS_TOKEN>' \ https://<BRX_GRAPHQL_SERVICE_HOST>/signin
The following diagram depicts an high level interaction flow to refresh the access token. In this specific example, it is assumed that the visitor triggers some operations (e.g. add to cart) but in the meantime the access data has expired.