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
&authenticated
unless 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"
}