How to create a JWT
Creating a JSON Web Token takes five decisions: the claims, the lifetime, the algorithm, the key and the header. This walkthrough covers each, then shows the code in seven languages.
1. Decide the claims
Start with who the token is about (sub), who issues it (iss) and who should accept it (aud). Add only the application claims verifiers need — roles or scopes, a tenant ID. Anything in the payload is readable by the token holder. See building JWT claims.
2. Set the lifetime
Set iat to now and exp to now plus a short duration — minutes for access tokens. Times are seconds since the epoch; the timestamp builder converts “15 minutes” into the exact value.
3. Choose the algorithm
HS256 if the issuer is the only verifier; RS256 or ES256 if others verify. See choosing an algorithm.
4. Get a key
For HS256, a random 32-byte secret. For RS256/ES256, a private key — generate one with the key generator for testing, or in your KMS for production.
5. Build the header
alg must match the key; typ is usually JWT (or at+jwt for OAuth access tokens); add kid when verifiers choose from several keys.
Code in seven languages
JavaScript
import { SignJWT } from "jose";
const key = new Uint8Array(Buffer.from(process.env.JWT_SECRET, "base64url"));
const now = Math.floor(Date.now() / 1000);
const payload = {
iss: "https://auth.example.com/",
sub: "user-123",
aud: "api.example.com",
iat: now,
exp: now + 3600,
role: "editor",
};
const token = await new SignJWT(payload)
.setProtectedHeader({
alg: "HS256",
typ: "JWT",
})
.sign(key);
console.log(token);
TypeScript
import { SignJWT, type JWTPayload } from "jose";
const key = new Uint8Array(Buffer.from(process.env.JWT_SECRET!, "base64url"));
const now = Math.floor(Date.now() / 1000);
const payload: JWTPayload = {
iss: "https://auth.example.com/",
sub: "user-123",
aud: "api.example.com",
iat: now,
exp: now + 3600,
role: "editor",
};
const token = await new SignJWT(payload)
.setProtectedHeader({
alg: "HS256",
typ: "JWT",
})
.sign(key);
console.log(token);
Python
import os
import time
import base64
import jwt # pip install "pyjwt[crypto]"
secret = os.environ["JWT_SECRET"]
key = base64.urlsafe_b64decode(secret + "=" * (-len(secret) % 4))
now = int(time.time())
payload = {
"iss": "https://auth.example.com/",
"sub": "user-123",
"aud": "api.example.com",
"iat": now,
"exp": now + 3600,
"role": "editor",
}
token = jwt.encode(
payload,
key,
algorithm="HS256",
)
print(token)
Java
// com.nimbusds:nimbus-jose-jwt:10.x
import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.*;
import com.nimbusds.jwt.*;
import java.util.*;
public class Sign {
public static void main(String[] args) throws Exception {
byte[] secret = java.util.Base64.getUrlDecoder().decode(System.getenv("JWT_SECRET"));
JWSSigner signer = new MACSigner(secret);
long now = System.currentTimeMillis() / 1000L;
JWTClaimsSet claims = new JWTClaimsSet.Builder()
.issuer("https://auth.example.com/")
.subject("user-123")
.audience("api.example.com")
.issueTime(new Date((now) * 1000L))
.expirationTime(new Date((now + 3600) * 1000L))
.claim("role", "editor")
.build();
JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.HS256)
.type(new JOSEObjectType("JWT"))
.build();
SignedJWT jwt = new SignedJWT(header, claims);
jwt.sign(signer);
System.out.println(jwt.serialize());
}
}
Go
package main
import (
"encoding/base64"
"fmt"
"log"
"os"
"time"
"github.com/golang-jwt/jwt/v5"
)
func main() {
key, err := base64.RawURLEncoding.DecodeString(os.Getenv("JWT_SECRET"))
if err != nil {
log.Fatal(err)
}
now := time.Now().Unix()
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"iss": "https://auth.example.com/",
"sub": "user-123",
"aud": "api.example.com",
"iat": now,
"exp": now + 3600,
"role": "editor",
})
signed, err := token.SignedString(key)
if err != nil {
log.Fatal(err)
}
fmt.Println(signed)
}
C#
// dotnet add package Microsoft.IdentityModel.JsonWebTokens
using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Tokens;
var key = new SymmetricSecurityKey(Base64UrlEncoder.DecodeBytes(Environment.GetEnvironmentVariable("JWT_SECRET")));
var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
var claims = new Dictionary<string, object?>
{
["iss"] = "https://auth.example.com/",
["sub"] = "user-123",
["aud"] = "api.example.com",
["iat"] = now,
["exp"] = now + 3600,
["role"] = "editor",
};
var descriptor = new SecurityTokenDescriptor
{
Claims = claims,
SigningCredentials = new SigningCredentials(key, "HS256"),
};
// Do not add exp/iat/nbf automatically — use exactly the claims above.
var handler = new JsonWebTokenHandler { SetDefaultTimesOnTokenCreation = false };
Console.WriteLine(handler.CreateToken(descriptor));
PHP
<?php
// composer require firebase/php-jwt
require 'vendor/autoload.php';
use Firebase\JWT\JWT;
$key = JWT::urlsafeB64Decode(getenv('JWT_SECRET'));
$now = time();
$payload = [
'iss' => 'https://auth.example.com/',
'sub' => 'user-123',
'aud' => 'api.example.com',
'iat' => $now,
'exp' => $now + 3600,
'role' => 'editor',
];
$token = JWT::encode($payload, $key, 'HS256', null);
echo $token . PHP_EOL;
Test it
Decode the result in JWTDecoder to confirm the claims and timeline, and run your API against the negative test tokens to make sure it rejects expired, wrong-audience and tampered tokens.
Build it visually and copy the exact code that reproduces it.
Create a JWT now