
# Application OAuth 2.0 Authentication

<Note type="info">

This page covers OAuth 2.0 for **applications** — an alternative to [API keys](/api/authentication) 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.

</Note>

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:

1. **An application registered with Gigs.** Your Gigs contact will share your `client_id`.
2. **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`**: a `client_secret`, a high-entropy secret string, shared by Gigs via a secure channel (e.g. a 1Password share).
3. **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.
4. **A confidential, server-side environment.** The private key or `client_secret` must never be embedded in mobile apps, browser code, or any other client that can be inspected by end users.

<Note type="warning">

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.

</Note>

## 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](https://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication).

### One-time setup

1. **Generate an RSA keypair** (2048 bits or larger). For example:

<CodeGroup title="Generating an RSA keypair with OpenSSL">

```shell
openssl genrsa -out gigs_signing_key.pem 2048
openssl rsa -in gigs_signing_key.pem -pubout -out gigs_signing_key.pub.pem
```

</CodeGroup>

2. **Share the public key with Gigs.** Send your public key in PEM format (the `.pub.pem` file from step 1). Gigs converts it to JWK and registers it on your application. The key thumbprint is used as `kid` following 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 `kid` you 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**

<CodeGroup title="Client assertion header">

```json
{
  "alg": "PS256",
  "kid": "NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs"
}
```

</CodeGroup>

- `alg` must be one of `RS256` or `PS256`. `PS256` (RSA-PSS) is the modern recommendation and is preferred.
- `kid` must 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. |

<CodeGroup title="Example client assertion payload">

```json
{
  "iss": "tCa9bK2mLp5nQr8sVx1wY3zA7dF0gH4j",
  "sub": "tCa9bK2mLp5nQr8sVx1wY3zA7dF0gH4j",
  "aud": "https://auth.gigs.com/oauth/token",
  "jti": "8e3c1a2c-3a82-4b9b-9b71-2cf9a55a73f2",
  "iat": 1746979200,
  "exp": 1746979260
}
```

</CodeGroup>

### Token request

Send the assertion in the form body. **No `Authorization` header is used.**

<CodeGroup title="Token endpoint (private_key_jwt)">

```text
POST https://auth.gigs.com/oauth/token
Content-Type: application/x-www-form-urlencoded
```

</CodeGroup>

| 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. |

<CodeGroup title="Requesting a token with private_key_jwt - cURL">

```shell
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
```

</CodeGroup>

## 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](https://datatracker.ietf.org/doc/html/rfc6749#section-2.3.1)) to send your `client_id` and `client_secret`.

### Token request

<CodeGroup title="Token endpoint (client_secret_basic)">

```text
POST https://auth.gigs.com/oauth/token
Authorization: Basic <base64(client_id:client_secret)>
Content-Type: application/x-www-form-urlencoded
```

</CodeGroup>

| 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. |

<CodeGroup title="Requesting a token with client_secret_basic - cURL">

```shell
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
```

</CodeGroup>

## Successful response (`200 OK`)

Both authentication methods return the same response:

<CodeGroup title="A successful token response">

```json
{
  "access_token": "eyJraWQiOiI...c2lnbmF0dXJl",
  "expires_in": 3600,
  "token_type": "Bearer"
}
```

</CodeGroup>

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**

<CodeGroup title="Authorization header">

```text
Authorization: Bearer <access_token>
```

</CodeGroup>

<CodeGroup title="Calling the Gigs API with an access token - cURL">

```shell
curl https://api.gigs.com/projects/gigs-project/subscriptions \
  -H "Accept: application/json" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

</CodeGroup>

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 `kid` in the header to the new thumbprint value. Ask Gigs to remove the old key.

## Good practices

- **Prefer `private_key_jwt` over `client_secret_basic`.** If you're starting fresh, set up `private_key_jwt` from day one. If you're already using `client_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.
