OAuth 2.0 JWT Bearer

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)

  1. Register an OAuth Application and upload your public RSA key (PEM).
  2. Your server builds and signs a JWT (RS256) with required claims (iss, sub, aud, exp, iat; optional scope).
  3. Your server POSTs the JWT to the token endpoint with grant type urn:ietf:params:oauth:grant-type:jwt-bearer.
  4. If valid, we return a Bearer access token (300 s TTL).
  5. 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

  1. Register an OAuth Application and upload your public RSA key (PEM).
  2. Your backend creates a JWT assertion with required claims and signs it with RS256.
  3. POST the assertion to the Token Endpoint using the grant type urn:ietf:params:oauth:grant-type:jwt-bearer.
  4. Receive a Bearer access token (valid 300 seconds).
  5. 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 always Bearer.
  • expires_in is always 300 (seconds).
  • refresh_token is not returned for this grant.

4) Certificate (Public Key) Upload

  1. Generate an RSA key pair (min. 2048 bits).
  2. Keep the private key secure on your server (never share it).
  3. Upload the public key (PEM) to your OAuth Application settings.
  4. 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

ClaimNameRulesExample
issIssuerMust equal your OAuth Application Client ID"9b3c...client-id"
subSubjectMust be the email of an activated user who is a member of the same space as the OAuth Application"[email protected]"
audAudienceMust exactly match the Token Endpoint URL"https://api.example.com/oauth2/token"
expExpiration time (UTC seconds).Must be in the future, keep short (≤ 60s).1692288000
iatIssued-at time (UTC seconds).Must be in the past.1692287940

Optional Claim

ClaimDescriptionExample
scopeSpace-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:

errorerror_descriptionWhen it happens
jwt_bearer_missing_assertionJWT Bearer assertion is missing.assertion param not provided.
jwt_bearer_invalidJWT Bearer token is invalid.Generic failure.
jwt_bearer_invalid_signatureJWT Bearer token has an invalid signature.Signature can’t be verified with your uploaded public key.
jwt_bearer_expiredJWT Bearer token has expired.exp in the past.
jwt_bearer_invalid_issuerJWT Bearer token has an invalid issuer.iss not equal to your OAuth App Client ID, or app not found.
jwt_bearer_invalid_audienceJWT Bearer token has an invalid audience.aud not equal to the token endpoint URL.
jwt_bearer_invalid_userJWT 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.