Authenticate backend systems without user interaction. This flow enables server-to-server integrations with short-lived, signed tokens and optional user impersonation.
OAuth 2.0 JWT Bearer Flow
Overview
Many enterprise integrations need server-to-server authentication without any interactive user login. The JWT Bearer Grant (RFC 7523) lets your backend exchange a client-signed JWT for a short-lived OAuth 2.0 Bearer access token. You sign a JWT with your private key; we verify it with the public key you uploaded to your OAuth Application and return a time-boxed access token you can use to call our API.
Why we offer this
- Enterprise-grade trust: Key-based trust between your system and Productboard—no long-lived shared secrets.
- User attribution: Request tokens on behalf of a specific user in your space (via
sub
) so actions are authorized and auditable. - Just-in-time access: Tokens are short-lived (5 min) and no refresh tokens are issued—simply re-assert as needed for strong replay resistance.
- Least privilege: Request only the scopes you need; we issue a token limited to your app’s allowed subset.
When to use this flow
Use the JWT Bearer Grant when:
- Your integration runs entirely on the server (no browser/user consent screen).
- You need strong coupling between systems (stable, automated jobs, backend-to-backend pipelines).
- You must act on behalf of individual users in your Productboard space (e.g., sync items as a specific user for correct permissions and audit).
If you require end-user sign-in or interactive consent, use a standard OAuth authorization_code
flow instead.
How it works (at a glance)
- Register an OAuth Application and upload your public RSA key (PEM).
- Your server builds and signs a JWT (
RS256
) with required claims (iss
,sub
,aud
,exp
,iat
; optionalscope
). - Your server POSTs the JWT to the token endpoint with grant type
urn:ietf:params:oauth:grant-type:jwt-bearer
. - If valid, we return a Bearer access token (300 s TTL).
- You call the API with
Authorization: Bearer <token>
.
Key capabilities
- Act-on-behalf-of a user: Provide
sub
as the email of an activated member of the same space as your OAuth App. - Fine-grained scopes: Request a subset of your application’s scopes via
scope
; we return the intersection. - Short-lived tokens: Access tokens for this grant type default to 5 minutes.
- No refresh tokens: This flow does not mint refresh tokens. Re-send a new JWT assertion when needed.
Security model
- Asymmetric crypto: You keep your private key; we store your public key on the OAuth Application and use it to verify signatures.
- Exact audience:
aud
must match the token endpoint URL to prevent token reuse against other services. - Tight time windows:
iat
in the past;exp
in the near future; issued tokens live 300 s to reduce replay risk. - Key rotation: Upload a new public key, start signing with the new private key, then remove the old key after cutover.
- No shared secrets for this flow: For JWT Bearer apps we don’t issue client secrets; the certificate is the trust anchor.
Prerequisites
- An OAuth Application in Productboard with your public RSA key (PEM) uploaded.
- A user in your space to act on behalf of (
sub
). The user must be activated and a member of the same space.
Note on customers without their own certificate This flow requires you to provide your public key; we do not generate or host keys on your behalf.
Typical use cases
- CRM/ITSM connectors (nightly syncs, ticket ↔ feature linkage) that run unattended and attribute changes to a real user.
- Data pipelines performing scheduled exports/imports with scoped, expiring tokens.
- Internal automations where your platform calls Productboard as a specific user to respect permissions and audit.
Integration Guide
Purpose: Enable server-to-server authentication using a client-signed JWT (RFC 7523) to obtain a short-lived OAuth 2.0 Bearer access token for calling the API.
1) Terminology (Token Dictionary)
- Connected App: Your application that integrates with our API.
- OAuth Application (Client): Your integration registered in our system. Identified by Client ID (
iss
in JWT). Stores your public key used to verify your JWT signature. - JWT (Assertion): A signed JSON Web Token you create and send to the token endpoint as
assertion
. Signed with your private key using RS256. - Access Token: Short-lived token returned by our
/oauth2/token
endpoint. Type is Bearer; lifetime 300s (5 min); no refresh token is issued for this flow. - Scopes: Space-separated permissions such as
users:read
. The requested scope must be a subset of scopes granted to your OAuth Application; otherwise it’s trimmed to the allowed subset. - Resource Owner: A valid, activated user who is a member of the same space as the OAuth Application. Their email appears in
sub
.
2) High-Level Flow
- Register an OAuth Application and upload your public RSA key (PEM).
- Your backend creates a JWT assertion with required claims and signs it with RS256.
- POST the assertion to the Token Endpoint using the grant type
urn:ietf:params:oauth:grant-type:jwt-bearer
. - Receive a Bearer access token (valid 300 seconds).
- Call the API with
Authorization: Bearer <token>
.
3) Endpoints
Token Endpoint
POST {APP_DEFAULT_URL}/oauth2/token
Content-Type: application/x-www-form-urlencoded
grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer
&assertion=<your-signed-jwt>
Successful Response (200):
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 300,
"scope": "users:read"
}
Notes
token_type
is alwaysBearer
.expires_in
is always300
(seconds).refresh_token
is not returned for this grant.
4) Certificate (Public Key) Upload
- Generate an RSA key pair (min. 2048 bits).
- Keep the private key secure on your server (never share it).
- Upload the public key (PEM) to your OAuth Application settings.
- Rotate keys when needed:
- Upload the new public key.
- Start signing JWTs with the new private key.
- Remove the old key after traffic fully migrates.
Requirements
- Signature algorithm: RS256 (RSA with SHA-256).
- Public key must be a valid PEM.
5) JWT Requirements
Required Claims
Claim | Name | Rules | Example |
---|---|---|---|
iss | Issuer | Must equal your OAuth Application Client ID | "9b3c...client-id" |
sub | Subject | Must be the email of an activated user who is a member of the same space as the OAuth Application | "[email protected]" |
aud | Audience | Must exactly match the Token Endpoint URL | "https://api.example.com/oauth2/token" |
exp | Expiration time (UTC seconds). | Must be in the future, keep short (≤ 60s). | 1692288000 |
iat | Issued-at time (UTC seconds). | Must be in the past. | 1692287940 |
Optional Claim
Claim | Description | Example |
---|---|---|
scope | Space-separated scopes; must be a subset of the app’s allowed scopes. If omitted, the app’s default scopes are used. | "users:read users_pii:read" |
Example Payload:
{
"iss": "YOUR_CLIENT_ID",
"sub": "[email protected]",
"aud": "https://api.example.com/oauth2/token",
"exp": 1692288000,
"iat": 1692287940,
"scope": "users:read"
}
6) End-to-End Example
sequenceDiagram participant Client as Client Application participant Authz as Authorization Server (STS) participant Gateway as API Gateway participant Service as Service %% 1. Client obtains or builds JWT assertion Client-->>Client: Issue JWT %% 2. Exchange JWT for access token Client->>Authz: POST /oauth2/token Authz-->>Authz: Validate JWT signature & claims Authz-->>Gateway: Register access token Authz-->>Client: 200 OK { "access_token": "...", ... } %% 3. Call protected API Client->>Gateway: GET api.pb.com/resource Authorization: Bearer <token> Gateway-->>Gateway: Validate access token Gateway->>Service: GET pb-service/resource X-Headers Service-->>Gateway: 200 OK data Gateway-->>Client: 200 OK data
6.1 Create and Sign the JWT (RS256)
Bash:
# Generate RSA key pair (2048 bits)
#!/bin/bash
# This script regenerates the RSA key pair for JWT testing
# It ensures the keys are properly formatted and match each other
set -e
echo "Generating a fresh RSA key pair for testing..."
openssl genrsa -out temp_private_key.pem 2048
openssl rsa -in temp_private_key.pem -pubout -out temp_public_key.pem
# Copy files without any modifications to ensure formatting is preserved
cp temp_private_key.pem rsa_private_key.pem
cp temp_public_key.pem rsa_public_key.pem
# Clean up temporary files
rm temp_private_key.pem
rm temp_public_key.pem
# Verify the key pair works together
echo "Verifying keys match..."
KEY_MATCH=$(openssl rsa -in rsa_private_key.pem -noout -modulus | openssl md5)
PUB_MATCH=$(openssl rsa -in rsa_public_key.pem -pubin -noout -modulus | openssl md5)
if [ "$KEY_MATCH" = "$PUB_MATCH" ]; then
echo "SUCCESS: Keys match properly: $KEY_MATCH"
else
echo "ERROR: Keys don't match!"
echo "Private key modulus: $KEY_MATCH"
echo "Public key modulus: $PUB_MATCH"
fi
echo "Keys generated at:"
echo "- rsa_private_key.pem"
echo "- rsa_public_key.pem"
Ruby:
require "jwt"
require "openssl"
private_key = OpenSSL::PKey::RSA.new(File.read("rsa_private_key.pem"))
payload = {
iss: ENV["CLIENT_ID"],
sub: "[email protected]",
aud: "#{ENV['APP_DEFAULT_URL']}/oauth2/token",
exp: (Time.now + 60).to_i,
iat: Time.now.to_i,
scope: "users:read"
}
assertion = JWT.encode(payload, private_key, "RS256")
puts assertion
7) Error Handling
The token endpoint returns 400
for validation failures. Possible error
values:
error | error_description | When it happens |
---|---|---|
jwt_bearer_missing_assertion | JWT Bearer assertion is missing. | assertion param not provided. |
jwt_bearer_invalid | JWT Bearer token is invalid. | Generic failure. |
jwt_bearer_invalid_signature | JWT Bearer token has an invalid signature. | Signature can’t be verified with your uploaded public key. |
jwt_bearer_expired | JWT Bearer token has expired. | exp in the past. |
jwt_bearer_invalid_issuer | JWT Bearer token has an invalid issuer. | iss not equal to your OAuth App Client ID, or app not found. |
jwt_bearer_invalid_audience | JWT Bearer token has an invalid audience. | aud not equal to the token endpoint URL. |
jwt_bearer_invalid_user | JWT Bearer token subject does not match a valid user. | sub email not found/activated/not in the same space. |
8) Security & Best Practices
- Short TTL: Keep
exp
within ~60 seconds and request the token just-in-time. - Clock Skew: Ensure NTP is configured;
iat
must be in the past,exp
in the future. - Key Management: Protect your private key; rotate keys periodically.
- Least Privilege: Request only the minimal
scope
you need. - No Refresh Token: This grant intentionally issues no refresh token. Re-assert as needed.
- Audience Exact Match:
aud
must exactly equal the token endpoint URL. - Tenant Boundaries:
sub
must identify an activated user who is a member of the OAuth App’s space.