Application OAuth 2.0 Authentication
This page covers OAuth 2.0 for applications — an alternative to API keys for server-to-server access. Unlike static API keys, which grant full access to a single project, an application fetches short-lived access tokens and can be granted scoped roles across multiple projects.
Gigs facilitates machine-to-machine authentication by allowing an application to fetch a short-lived access token, which can be used to call the Gigs API.
Key properties
- Server-to-server only. The flow is for backend services. There is no end-user consent step.
- Multi-project access. Your application can be allowed access to multiple projects.
- Short-lived. Access tokens expire after 1 hour. There is no refresh-token flow — request a new token when the current one is close to expiring.
- [Optional] Project-scoped tokens. By default access tokens have access to all projects that the application has a role for. Optionally, you can request access tokens that are bound to a single Gigs project, if you want to reduce the blast radius of a leaked token. To work across multiple projects, request a separate token per project.
Servers
| Role | Host |
|---|---|
| Authorization Server | https://auth.gigs.com |
| Resource Server | https://api.gigs.com |
Prerequisites
Currently, this configuration requires coordination with Gigs; however, we are scoping the capability further to enable self-service application management. As of now, before you start, you need:
- An application registered with Gigs. Your Gigs contact will share your
client_id. - Client credentials, depending on the authentication method you'll use:
- For
private_key_jwt: an RSA keypair (2048 bits or larger). You generate it; you share the public key with Gigs (see the section on private key authentication below). The private key never leaves your environment. - For
client_secret_basic: aclient_secret, a high-entropy secret string, shared by Gigs via a secure channel (e.g. a 1Password share).
- For
- Project access configured. Your application must be granted a role in each project you need to access. Until self-service is available, contact Gigs to set this up. Without an active grant, token requests for that project will be rejected.
- A confidential, server-side environment. The private key or
client_secretmust never be embedded in mobile apps, browser code, or any other client that can be inspected by end users.
Treat your private key (or client_secret) like a password. If it leaks, contact Gigs immediately so we can rotate or revoke the corresponding key.
Authenticating with private_key_jwt (recommended)
In this flow, you authenticate by sending a short-lived JWT — the client assertion — signed with your private key. Gigs verifies the signature against the public key you registered with us. This follows OpenID Connect Core 1.0 §9.
One-time setup
- Generate an RSA keypair (2048 bits or larger). For example:
Generating an RSA keypair with OpenSSL
openssl genrsa -out gigs_signing_key.pem 2048
openssl rsa -in gigs_signing_key.pem -pubout -out gigs_signing_key.pub.pem
-
Share the public key with Gigs. Send your public key in PEM format (the
.pub.pemfile from step 1). Gigs converts it to JWK and registers it on your application. The key thumbprint is used askidfollowing RFC 7638 and we will share it back to you.Multiple keys can be registered to support rotation; when more than one is registered, the
kidyou include in the assertion header tells us which to verify against.
Building the client assertion
For each token request, construct a fresh JWT signed with your private key.
Header
Client assertion header
{
"alg": "PS256",
"kid": "NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs"
}
algmust be one ofRS256orPS256.PS256(RSA-PSS) is the modern recommendation and is preferred.kidmust match the thumbprint of a registered key. Required when you have more than one key registered; otherwise optional but recommended.
Payload
| Claim | Required | Value |
|---|---|---|
iss | Yes | Your client_id. |
sub | Yes | Your client_id (same as iss). |
aud | Yes | The token endpoint URL: "https://auth.gigs.com/oauth/token". |
jti | Yes | A unique, non-empty string per assertion (e.g. a UUIDv4). Prevents replay. |
iat | Yes | Issued-at, as a UNIX timestamp. Must not be in the future (small clock skew is tolerated). |
exp | Yes | Expiry, as a UNIX timestamp. Must be after iat, and the assertion lifetime (exp - iat) must not exceed 300 seconds. |
Example client assertion payload
{
"iss": "tCa9bK2mLp5nQr8sVx1wY3zA7dF0gH4j",
"sub": "tCa9bK2mLp5nQr8sVx1wY3zA7dF0gH4j",
"aud": "https://auth.gigs.com/oauth/token",
"jti": "8e3c1a2c-3a82-4b9b-9b71-2cf9a55a73f2",
"iat": 1746979200,
"exp": 1746979260
}
Token request
Send the assertion in the form body. No Authorization header is used.
Token endpoint (private_key_jwt)
POST https://auth.gigs.com/oauth/token
Content-Type: application/x-www-form-urlencoded
| Name | Required | Description |
|---|---|---|
grant_type | Yes | Must be "client_credentials". |
client_assertion_type | Yes | Must be "urn:ietf:params:oauth:client-assertion-type:jwt-bearer". |
client_assertion | Yes | The signed JWT assertion built above. |
scope | No | The project to authenticate for, in the form "project:{project_id}" (e.g. "project:gigs-project"). Only if you want to request a project-scoped token. |
Requesting a token with private_key_jwt - cURL
curl https://auth.gigs.com/oauth/token \
-d "grant_type=client_credentials" \
-d "client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer" \
-d "client_assertion=$CLIENT_ASSERTION" \
-d "scope=project:gigs-project" # optional, for a project-scoped token
Authenticating with client_secret_basic
Simpler to set up, but Gigs holds a copy of your secret — prefer private_key_jwt for production. Uses HTTP Basic authentication (RFC 6749 §2.3.1) to send your client_id and client_secret.
Token request
Token endpoint (client_secret_basic)
POST https://auth.gigs.com/oauth/token
Authorization: Basic <base64(client_id:client_secret)>
Content-Type: application/x-www-form-urlencoded
| Name | Required | Description |
|---|---|---|
grant_type | Yes | Must be "client_credentials". |
scope | No | The project to authenticate for, in the form "project:{project_id}" (e.g. "project:gigs-project"). Only if you want to request a project-scoped token. |
Requesting a token with client_secret_basic - cURL
curl https://auth.gigs.com/oauth/token \
-u "$GIGS_CLIENT_ID:$GIGS_CLIENT_SECRET" \
-d "grant_type=client_credentials" \
-d "scope=project:gigs-project" # optional, for a project-scoped token
Successful response (200 OK)
Both authentication methods return the same response:
A successful token response
{
"access_token": "eyJraWQiOiI...c2lnbmF0dXJl",
"expires_in": 3600,
"token_type": "Bearer"
}
where
| Field | Description |
|---|---|
access_token | The JWT to send on subsequent API requests. Treat as opaque — do not parse, log, or persist beyond what's needed for caching. |
expires_in | Lifetime in seconds. Currently 3600 (1 hour). |
token_type | Always "Bearer". |
Calling the Gigs API
Send the access token as a Bearer token on every request to https://api.gigs.com.
Header
Authorization header
Authorization: Bearer <access_token>
Calling the Gigs API with an access token - cURL
curl https://api.gigs.com/projects/gigs-project/subscriptions \
-H "Accept: application/json" \
-H "Authorization: Bearer $ACCESS_TOKEN"
If you opted for a project-scoped access token, the token's project claim must match the {project} segment in the request path. Requesting a different project's resources will be rejected with 403 Forbidden.
Why we recommend private_key_jwt?
- No shared secret. Gigs only holds your public key, so a breach on our side cannot expose a credential that impersonates you.
- Short-lived authentication. Each assertion is signed, scoped to our token endpoint, valid for at most 300 seconds, and replay-protected via
jti. - Maintainability. Rotate keys with no downtime: send us your new public key (in PEM format), we compute the thumbprint and send it back to you. Start using your new key, and switch the
kidin the header to the new thumbprint value. Ask Gigs to remove the old key.
Good practices
- Prefer
private_key_jwtoverclient_secret_basic. If you're starting fresh, set upprivate_key_jwtfrom day one. If you're already usingclient_secret_basic, you can register a signing key alongside it and migrate at your own pace; both methods can coexist on the same application. - Cache tokens. Reuse a token until it's close to expiring (e.g. < 60 seconds left) instead of requesting a new one per API call. On a
401 Unauthorized, discard the cached token, fetch a new one, and retry once.