JWT Assertion
Overview
The JWT assertion is a JSON Web Token (JWT) that is used to authenticate a client application with the Peddler API. The JWT assertion is signed with a private key and contains the following claims:
iss(issuer) - The client id of the applicationsub(subject) - The user id of the resource owneraud(audience) - The Peddler API base URL token pathexp(expiration time) - The expiration time of the JWT assertioniat(issued at) - The time the JWT assertion was issued
The JWT assertion is used to obtain an accessToken which is used to make requests to protected endpoints and access/create/modify underlying resources.
JWT Assertion creation
The JWT assertion is created by signing a JSON object with the private key. The JSON object contains the claims listed above. The JWT assertion is then sent to the Peddler API to obtain an accessToken.
The JWT assertion is created using the following steps:
- Create a JSON object containing the claims listed above
 - Sign the JSON object with the private key
 - Encode the signed JSON object using Base64 URL encoding
 
- NodeJS
 - PHP
 - Python
 
const jwt = require('jws');
// Load the SSL credentials
const sslCerts = require('./../private/ssl_cert'); // RSA KEY PROVIDED
// Construct the claim
const payload = {
  iss: '123', // issuer - client id
  sub: 'bob', // subject - the resource owner id/username/email
  aud: '/oauth/token', // audience
  exp: Date.now() + 10000, // expiration time of claim  ISO-8601 
  iat: Date.now(), // issued at time of claim  ISO-8601 
  scope: ['DEFAULT', 'authenticated'] // a list of oAuth 2.0 scopes
};
const body = {
  header: { alg: 'RS256' },
  privateKey: sslCerts.privateKey,
  payload: payload
};
// Create a JWT assertion
const assertion = jwt.sign(body);
<?php
class JWTBuilder
{
    const DEFAULT_SCOPE = ['DEFAULT', 'authenticated'];
    const API_URL = 'https://api-lokl.peddler.com/';
    const SANDBOX_API_URL = 'https://alphadev-api.peddler.com/';
    const GRANT_TYPE = 'urn:ietf:params:oauth:grant-type:jwt-bearer';
    protected InMemory $privateFile;
    protected string $clientId = '';
    protected string $storeOwnerId = '';
    protected string $storeId = '';
    protected bool $isSandbox = false;
    public function __construct(
        string $privateFile,
        string $clientId,
        string $storeOwnerId,
        string $storeId,
        bool $isSandbox = true
    ) {
        if (preg_match('/-{3,}\n([\s\S]*?)\n-{3,}/', $privateFile)) {
            $this->privateFile = InMemory::plainText($privateFile);
        } else {
            $this->privateFile = InMemory::file($privateFile);
        }
        $this->clientId = $clientId;
        $this->storeOwnerId = $storeOwnerId;
        $this->storeId = $storeId;
        $this->isSandbox = $isSandbox;
    }
    /**
     * Generate JWT Claim required for
     * @return string
     */
    public function generateJWTClaim(): string
    {
        $configuration = $this->getJwtConfiguration();
        $now = new DateTimeImmutable();
        $token = $configuration->builder()
            // Configures the issuer (iss claim)
            ->issuedBy($this->clientId)
            // Configures the sub
            ->relatedTo($this->storeOwnerId)
            // Configures the audience (aud claim)
            ->permittedFor('/oauth/token')
            // Configures the id (jti claim)
            // Configures the time that the token was issue (iat claim)
            ->issuedAt($now)
            // Configures the expiration time of the token (exp claim)
            ->expiresAt($now->modify('+10000 seconds'))
            // Configures a new claim, called "uid"
            ->withClaim('scope', self::DEFAULT_SCOPE)
            ->withClaim('storeId', $this->storeId)
            // Builds a new token
            ->getToken($configuration->signer(), $configuration->signingKey());
        return $token->toString();
    }
    private function getJwtConfiguration(): Configuration
    {
        return Configuration::forAsymmetricSigner(new Sha256(), $this->privateFile, $this->privateFile);
    }
    public function isSandboxEnabled(): bool
    {
        return $this->isSandbox == true;
    }
    public function getApiUrl(): string
    {
        return $this->isSandboxEnabled() ? self::SANDBOX_API_URL : self::API_URL;
    }
}
import os
import requests
import time
import jwt
import sys
def key_path(key_name):
    return os.path.join(os.path.dirname(os.path.realpath(__file__)), key_name)
def current_milli_time():
    return round(time.time() * 1000)
with open(key_path("testkey_rsa.priv")) as rsa_key:
            example_privkey = rsa_key.read()
url = "https://staging-api-lokl.peddler.com/oauth/token"
example_jwt = {
    "iss": "123",
    "sub": "bob",
    "aud": '/oauth/token',
    "exp": time.time()+100,
    "iat": time.time(),
    "scope": ['DEFAULT', 'authenticated'],
}
endcoded_jwt = jwt.encode(example_jwt, example_privkey, algorithm="RS256")
data = {
    "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
    "assertion": endcoded_jwt
}
headers = {"Content-type": "application/x-www-form-urlencoded"}
# get the access token
auth_res = requests.post(url, headers=headers, data=data)
if auth_res.status_code == 200:
    # if response is OK, get the access token
    print("[!] Got access token:", auth_res)
else:
    print("[!] Cannot get access token, exiting...", auth_res)
    exit()
- Scopes are always 
DEFAULT&authenticatedunless instructed otherwise. - Client authentication with JWT spec a refresh token is not generated.
 
JWT Assertion for an access token
The JWT assertion is sent to the Peddler API to obtain an accessToken. The accessToken is used to make requests to protected endpoints and access/create/modify underlying resources.
const form = {
  grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
  assertion: assertion
};
const request = require('request');
request.post({
  url: '/oauth/token',
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded'
  },
  form: form
}, function(err, res, body) {
  console.log(err, body);
});
Receiving the bearer token (in the body, in json)
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
Pragma: no-cache
{
  "access_token":"FkzdyJV14zc73AaK9FmNCtyp5bUTegis",
  "expires_in":7200,
  "scope":"DEFAULT authenticated",
  "token_type":"Bearer"
}
OR
HTTP/1.1 403 FORBIDDEN
Content-Type: application/json
Cache-Control: no-store
Pragma: no-cache
{
  "error":"access_denied",
  "error_description":"Invalid subject: test" 
}