Skip to content

hamidra/jw3t

Repository files navigation

⚠️ This library is not audited and the spec is still a work in progress and likely to change .

What is Json Web3 Token

Json Web3 Token (JW3T) is a self-contained json token, inspired by Json Web Token standard RFC 7519 with some adaptations to make it work for the web3 authentications and authorization usecases.

JW3T vs JWT?

The main difference between a "Json Web3 Token" and "Json Web Token" is in the issuance and verification procedures. In web3 world, the identities are self-sovereign and each user owns the signing keys of their accounts, hence the Json Web3 Tokens are issued by the users themselves and are signed by the user account's private key. This means that the issuer and the subject of a JW3T are the same which makes the fields redundant. Also another characteristic of web3 accounts is that the address of an account can be derived from the signature (using the public key). Considering these chracteristics we can replace the issuer and subject fields with an account address field. So for jw3t:

  • The token includes an address claim in the payload.
  • The token is signed by the private key of the claimed address.
  • The token is considered to be valid if it has a valid signature, and the signature address matches the claimed address in the payload.

Why JW3T?

Why do we need a token for web3 where all messages can be signed by the owner accounts. If we are developing decentralized apps then who are these tokens issued for?

The advantage the Json Web3 Tokens provide is the better user experience for scenarios that there is a trusted middle layer between the user and decentralized network. This can happen when there are some hybrid scenarios that might require to access some off-chain services and data as well as onchain transactions.

E.g in the case of NFTs there are minting platforms that provide an e2e UX for minting and managing NFTs which let the user upload the resources to IPFS and set the metadata fields on the chain. In these scenarios the platform needs to authneticate the users accounts to be able to let them manage their off-chain resources. JW3T can provide a self-contained and self-sovereign solution for the user to authenticate and authorize their off-chain resources.

How JW3T is issued?

Similar to Json Web Tokens, Json Web3 Tokens consist of the same three parts (Header, Payload, Signature) encoded in Base64URI and separated by dots (.)

The final token format would look like below:
xxxxx.yyyyy.zzzzz

Header:

The header includes the signing scheme specified by "algorithm" field and the address type specified by "address_type" field and the token type specified by "token_type" which is always set to "JW3T" for web3 tokens.

e.g.

{
  "algorithm": "sr25519",
  "address_type": "ss58",
  "token_type": "JW3T"
}

specifies that the token is a Json Web3 Token that is using sr25519 signing schema and the address type is a substrate address.

Payload:

The payload of the token is a json object which includes a required address claim that species the address of the account that has issued the token as well as some suggested claim fields:

  • "address" (Address) Claim: specifies the address of the account that has signed the token. This field is used during the token verification and if it is not a valid address of the type that is specified in the header {"address_type":} the token verification should fail.
  • "audience" (Audience) Claim :
    The "audience" claim identifies the recipients that the JW3T is intended for. The audience is usually a uri that specifies the web resource or website that the token is issued for.
  • "expires_at" (Expiration Time) Claim:
    The "expires_at" (expiration time) claim identifies the expiration time on or after which the JW3T MUST NOT be accepted for processing. The processing of the "expires_at" claim requires that the current date/time MUST be before the expiration date/time listed in the "expires_at" claim.
  • "not_before" (Not Before) Claim:
    The "not_before" claim identifies the time before which the JWT MUST NOT be accepted for processing. The processing of the "not_before" claim requires that the current date/time MUST be after or equal to the not-before date/time listed in the "not_before" claim.

Signature:

The signature is generated by signing the header (as a JSON string) concatenated by a "." concatenated by the payload (as a JSON string) using the signing algorithm that is specified in the token header {"algorithm":}.

signature = sign(header + '.' + payload)

Note: Unlike JWT, that the signer signes the header and payload as a Base64URI encoded message, the header and payload in JW3T are signed as a JSON string to let the signer apps and wallets present them as json string so the user can see what message they are signing.

The token is then generated by Base64URI encoding each part and then concatenating them together separated by a ".". So the final token will be as:

jw3t =
  Base64URI.encode(header) +
  '.' +
  Base64URI.encode(payload) +
  '.' +
  Base64URI.encode(signature);

How JW3T is verified?

To verify if a JW3T is valid, two verifications must be performed:

  • the signatur should be verified to make sure it is a valid signature of the header + "." + payload.

  • the address claim in the payload payload.address should be a valid address of the address type that is specified in the header header.address_type and match the address of the account that has signed the message signature.address

How to use this package?

How to install:

yarn:
yarn add jw3t

npm:
npm i jw3t

How to use:

The package already includes a polkadotJS signer/verifier that can be used for token signing and verification.

ESM import

import * as jw3t from 'jw3t'

CJS require

const jw3t = require('jw3t')

e.g. to create a token signed by a substarte account:

// create a signing account
let keyring = new Keyring({ type: 'sr25519' });
let mnemonic = mnemonicGenerate();
let account = keyring.createFromUri(mnemonic);
let signingAccount = { account };
let address = account.address;

let header = {
  algorithm: 'sr25519',
  token_type: 'JW3T',
  address_type: 'ss58',
};

let payload = {
  address: address,
};

// set expire to 24 hours
let expires_at = Math.floor(Date.now() / 1000) + 24 * 3600;

let content = new jw3t.JW3TContent(header, payload)
  .setAudience('uri:test')
  .setExpiration(expires_at);

let polkaJsSigner = new jw3t.PolkaJsSigner(signingAccount);
let signer = new jw3t.JW3TSigner(polkaJsSigner, content);
let { base64Content, base64Sig } = await signer.getSignature();
let token = `${base64Content}.${base64Sig}`;

to verify a token is valid.

let token =
  'ewogImFsZ29yaXRobSI6ICJzcjI1NTE5IiwKICJ0b2tlbl90eXBlIjogIkpXM1QiLAogImFkZHJlc3NfdHlwZSI6ICJzczU4Igp9.ewogImFkZHJlc3MiOiAiNUdyd3ZhRUY1elhiMjZGejlyY1FwRFdTNTdDdEVSSHBOZWhYQ1BjTm9IR0t1dFFZIiwKICJub25jZSI6ICJmNzdiNzAiLAogIm9uX2JlaGFsZl9vZiI6ICI1RkhuZVc0NnhHWGdzNW1VaXZlVTRzYlR5R0J6bXN0VXNwWkM5MlVoakpNNjk0dHkiLAogInByb3h5X3R5cGUiOiAiZ292ZXJuYW5jZSIsCiAiYXVkaWVuY2UiOiAidXJpOnRlc3QiLAogImV4cGlyZXNfYXQiOiAxNjYwMDY3NDQ1Cn0.-GH6igp_L_egG0tJj18-hlZbllG0WliFa6JTEvLxa3RRvmVSD2gBHbFpNd0jaOTXLTpZ1asKCObtLYFw7jObhA';

let polkaJsVerifier = new jw3t.PolkaJsVerifier();
let verifier = new jw3t.JW3TVerifier(polkaJsVerifier);
let isValid = verifier.verify(token);