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: .. tab:: Python Open a terminal and navigate to your project directory, then run: .. code-block:: bash pip3 install PyJWT .. tab:: Node.js Open a terminal and navigate to your project directory, then run: .. code-block:: bash npm install jsonwebtoken .. tab:: Java .. code-block:: xml io.jsonwebtoken jjwt-api 0.11.5 io.jsonwebtoken jjwt-impl 0.11.5 runtime io.jsonwebtoken jjwt-jackson 0.11.5 runtime .. code-block:: groovy // 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: .. tab:: Python :new-set: .. code-block:: python 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) .. tab:: Node.js .. code-block:: javascript 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); .. tab:: Java .. code-block:: java 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: .. list-table:: :header-rows: 1 :widths: 15 85 * - 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: .. code-block:: bash curl -H "Authorization: Bearer " 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.