JWT Token Authentication
As an alternative to OAuth2 authentication, Cortex supports user authentication using JWT Tokens. We recommend this approach when a system outside of Elastic Path Commerce handles user authentication, and there is a trusted relationship between that system and Elastic Path.
The JWT Token contains details about the user who is making an API request. The token is signed using a private key that only the trusted system has, and verified using the associated public key that has been configured within Elastic Path Commerce.
The JWT Token can be passed to Cortex in the Authorization
header similar to an OAuth2 access token. Ensure that the base-64 encoded token must be prefixed with bearer
, similar to an OAuth access token.
JWT Token Format
The JWT Token might be formatted according to the RFC 7519 specification and is made up of a header, payload, and signature. The signature should be generated using the RS256 algorithm.
The following is an example of an expected JWT token payload:
{
"sub": "589f0eac-6bc3-4152-9d99-1f46800082ff",
"iss": "erp-backend",
"exp": 1580861590,
"iat": 1580857930,
"scope": "MOBEE",
"account": "929445a8-6827-4453-b37e-b434dbf3dc48",
"metadata": "eyAKICAgImZpcnN0LW5hbWUiOiJKb2huIiwKICAgImxhc3QtbmFtZSI6IkRvZSIsCiAgICJ1c2VyLWlkIjoiOTI5NDQ1YTgtNjgyNy00NDUzLWIzN2UtYjQzNGRiZjNkYzQ4IiwKICAgInVzZXItZW1haWwiOiJqb2huLmRvZUBlbWFpbC5jb20iLAogICAidXNlci1uYW1lIjoiam9obi5kb2UiLAogICAidXNlci1jb21wYW55IjoiU3RhcGxlcyIKfQ===="
}
Claim | Meaning | Elastic Path Commerce Usage |
---|---|---|
sub | Subject Identifier | Identifies the user to authenticate. The way this value is interpreted is dependent on the value of the COMMERCE/SYSTEM/CUSTOMER/identifier setting definition. The identified user must already exist in the Elastic Path Commerce database. If this value is omitted, Elastic Path Commerce creates a single-session user using the information in the metadata claim. |
iss | Issuer Identifier | Identifies the service that generated the token. This value is used as a context value when reading the value of the COMMERCE/SYSTEM/CUSTOMER/identifier setting definition. |
exp | Expiration Time | The date and time that the token should expire, in epoch seconds. Elastic Path will not accept an expired token. |
iat | Issued At Time | The date and time that the token was generated, in epoch seconds. This is not currently used, but might be used in the future. |
scope | Scope | The Cortex scope (store code) that might be used for the request. |
account | Account Identifier | The shared ID of the account the user is transacting on behalf of. This is an optional field and can be overridden by the x-ep-account-shared-id header in Cortex. |
metadata | JWT Metadata | A base-64-encoded representation of a JSON metadata object containing details about the user. This value is only read if the sub claim is omitted. |
The following is an example of an expected metadata payload:
{
"user-id": "929445a8-6827-4453-b37e-b434dbf3dc48",
"first-name": "John",
"last-name": "Doe",
"user-email": "john.doe@email.com"
}
The metadata is only used to generate a single-session user if the sub
claim is not specified. The fields are interpreted in the following table:
Field | Elastic Path Commerce Usage |
---|---|
user-id | The shared ID of the single-session user. If a record with this shared ID is found, it is used, otherwise a single-session user is created automatically. |
first-name | The user’s first name. |
last-name | The user’s last name. |
user-email | The user’s email. |
JWT Token Signing and Encoding
We strongly recommend the use of a JWT token generation library, such as Java JWT. For more information about libraries and a full list of libraries for various languages, see Libraries for Token Signing/Verification.
The following sample class illustrates how a JWT token could be generated:
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Base64;
import java.util.Date;
import java.util.List;
import javax.json.Json;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.stereotype.Component;
@Component
public class JwtTokenGenerator {
private static final String CLAIM_SCOPE = "scope";
private static final String CLAIM_ACCOUNT = "account";
private static final String CLAIM_METADATA = "metadata";
private static final String JWT_PRIVATE_KEY = "YOUR-PRIVATE-KEY";
public String createToken(final String issuer, final String subject, String scope,
final String account, final String metadata, final int expirationInSeconds)
throws InvalidKeySpecException, NoSuchAlgorithmException {
Instant now = Instant.now();
Date expiration = Date.from(now.plus(expirationInSeconds, ChronoUnit.SECONDS));
Date issuedAt = Date.from(now);
return Jwts.builder()
.setIssuer(issuer)
.setSubject(subject)
.setExpiration(expiration)
.setIssuedAt(issuedAt)
.claim(CLAIM_SCOPE, scope) // Multiple scopes are encoded into a single space delimited string
.claim(CLAIM_ACCOUNT, account)
.claim(CLAIM_METADATA, metadata)
.signWith(getPrivateKey())
.compact();
}
private PrivateKey getPrivateKey() throws NoSuchAlgorithmException, InvalidKeySpecException {
KeySpec privateKeySpec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(JWT_PRIVATE_KEY));
KeyFactory keyFactory = KeyFactory.getInstance(SignatureAlgorithm.RS256.getFamilyName());
return keyFactory.generatePrivate(privateKeySpec);
}
public String getMetadata(final String userId, String email, String firstName, String lastName) {
String json = Json.createObjectBuilder()
.add("user-id", userId)
.add("user-email", email)
.add("first-name", firstName)
.add("last-name", lastName)
.build()
.toString();
return Base64.getEncoder().encodeToString(json.getBytes());
}
}
The JWT token will only be accepted by Cortex once the corresponding public key is configured. For more information about how to do this, see Configuring PunchOut.