Authentication#

Self-signed JWT#

Before getting started, ensure a developer account has been created for you. You will receive an email containing your private key and key ID.

Please speak with your GroupVAN integrator to receive your client ID and user IDs.

API requests are authenticated with a short-lived JWT that you sign yourself using your private key. On each request, GroupVAN looks up the public key registered to your key ID and verifies the token — there is no login or token-exchange call.

You must implement a solution for minting a JWT using the key we created during the steps above — we have included some examples below on how to do this in a few different languages. If additional help or examples are needed, please reach out to your GroupVAN integrator.

First, install a JWT library:

Open a terminal and navigate to your project directory, then run:

pip3 install PyJWT

Open a terminal and navigate to your project directory, then run:

npm install jsonwebtoken
<!-- Maven (pom.xml) -->
<dependencies>
  <dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.11.5</version>
  </dependency>
  <dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.11.5</version>
    <scope>runtime</scope>
  </dependency>
  <dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId>
    <version>0.11.5</version>
    <scope>runtime</scope>
  </dependency>
</dependencies>
// Gradle (build.gradle)
dependencies {
    implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
    runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
    runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'
}

Then generate a token:

import jwt
from uuid import uuid4
from datetime import datetime, timedelta, timezone

private_key = YOUR_PRIVATE_KEY
key_id = YOUR_KEY_ID
client_id = YOUR_CLIENT_ID
user_id = YOUR_USER_ID

now = datetime.now(timezone.utc)
expires_at = now + timedelta(seconds=60)

token = jwt.encode(
    {
        'aud': 'groupvan',
        'iss': client_id,
        'kid': key_id,
        'sub': user_id,
        'iat': now,
        'exp': expires_at,
        'type': 'access',
        'jti': str(uuid4())
    },
    private_key,
    algorithm='RS256',
    headers={'gv-ver': 'GV-JWT-V1'}
)

print(token)
const jwt = require('jsonwebtoken');
const { randomUUID } = require('crypto');

const privateKey = YOUR_PRIVATE_KEY; // PEM string
const keyId = YOUR_KEY_ID;
const clientId = YOUR_CLIENT_ID;
const userId = YOUR_USER_ID;

const nowSeconds = Math.floor(Date.now() / 1000);
const expiresAtSeconds = nowSeconds + 60; // 60 seconds

const payload = {
  aud: 'groupvan',
  iss: clientId,
  kid: keyId,
  sub: userId,
  iat: nowSeconds,
  exp: expiresAtSeconds,
  type: 'access',
  jti: randomUUID()
};

const token = jwt.sign(payload, privateKey, {
  algorithm: 'RS256',
  header: { 'gv-ver': 'GV-JWT-V1' }
});

console.log(token);
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.time.Instant;
import java.util.Base64;
import java.util.Date;
import java.util.UUID;

public class JwtExample {
    public static void main(String[] args) throws Exception {
        String privateKeyPem = YOUR_PRIVATE_KEY; // PKCS#8 PEM string
        String keyId = YOUR_KEY_ID;
        String clientId = YOUR_CLIENT_ID;
        String userId = YOUR_USER_ID;
        PrivateKey privateKey = loadPrivateKeyFromPem(privateKeyPem);

        Instant now = Instant.now();
        Date iat = Date.from(now);
        Date exp = Date.from(now.plusSeconds(60));

        String token = Jwts.builder()
                .setAudience("groupvan")
                .setIssuer(clientId)
                .setSubject(userId)
                .setIssuedAt(iat)
                .setExpiration(exp)
                .claim("kid", keyId)
                .claim("type", "access")
                .setId(UUID.randomUUID().toString())
                .setHeaderParam("gv-ver", "GV-JWT-V1")
                .signWith(privateKey, SignatureAlgorithm.RS256)
                .compact();

        System.out.println(token);
    }

    private static PrivateKey loadPrivateKeyFromPem(String pem) throws Exception {
        String sanitized = pem
                .replace("-----BEGIN PRIVATE KEY-----", "")
                .replace("-----END PRIVATE KEY-----", "")
                .replaceAll("\\s", "");
        byte[] keyBytes = Base64.getDecoder().decode(sanitized.getBytes(StandardCharsets.UTF_8));
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory kf = KeyFactory.getInstance("RSA");
        return kf.generatePrivate(keySpec);
    }
}

How it works#

The code builds a JWT and signs it with your private key using the RS256 algorithm. GroupVAN verifies the signature with the public key registered to your key ID, so your private key never leaves your system.

The token header must include gv-ver: GV-JWT-V1, which identifies the token format. Tokens without it are rejected.

The payload claims:

Claim

Purpose

aud

Audience. Always groupvan.

iss

Issuer. Your client ID, identifying the application making the request.

kid

Key ID. Tells GroupVAN which public key to verify the signature with.

sub

Subject. The user ID the request acts on behalf of; must belong to your client.

iat / exp

Issued-at and expiration times. exp may be at most 60 seconds in the future; longer-lived tokens are rejected.

type

Token type. Always access.

jti

A unique ID for this token (any UUID). Allows individual tokens to be revoked.

Using the token#

Send the token in the Authorization header of each API request:

curl -H "Authorization: Bearer <token>" https://gateway.groupvan.com/json/federated/v3_2/...

Since tokens expire after 60 seconds, sign a fresh one for each request rather than caching — signing is fast and requires no network call.