Skip to main content

Certificate request tokens

This guide explains how to manage Certificate Request Tokens attached to a Frontdoor. Use these steps to list, create, update, and delete tokens. Certificate Request Tokens provide a secure, time-limited mechanism for generating client certificates without requiring direct API access.

Why this matters

  • Certificate Request Tokens enable delegated certificate creation without sharing API credentials.
  • The subject fields you set (Common Name, Organization, Organizational Unit) are enforced at enrollment, so you decide what identity the certificate claims.
  • Each token has a signed enrollment JWT that carries the endpoint, the token, and the subject in one artifact the end user can verify.
  • Tokens have expiration times to limit security exposure and can be revoked when no longer needed.

Assumptions

tip
  • Keep token names unique and descriptive (e.g., api-service-prod-token, ci-deployment-cert).
  • Pre-fill certificate metadata to ensure consistency across generated certificates.
  • Monitor token usage and clean up expired or unused tokens regularly.

Operations

List certificate request tokens

Request example:

curl -s \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/json" \
"https://gateway.production.netfoundry.io/frontdoor/3d6d2b6e-6c7a-4a7f-8c3d-9a9d2e1f0b1c/certificate-request-tokens?page=0&size=20&sort=name,asc"

Response example:

{
"content": [
{
"id": "token-123e4567-e89b-12d3-a456-426614174000",
"name": "api-service-prod",
"frontdoorId": "3d6d2b6e-6c7a-4a7f-8c3d-9a9d2e1f0b1c",
"token": "crt_1234567890abcdef",
"commonName": "api.example.com",
"organization": "Example Corp",
"organizationalUnit": "API Services",
"expiresAt": "2024-12-31T23:59:59Z",
"createdAt": "2024-01-15T10:30:00Z",
"createdBy": "user-456"
},
{
"id": "token-987fcdeb-51a2-43d7-8f9e-123456789abc",
"name": "ci-deployment-cert",
"frontdoorId": "3d6d2b6e-6c7a-4a7f-8c3d-9a9d2e1f0b1c",
"token": "crt_9876543210fedcba",
"commonName": "deploy.example.com",
"organization": "Example Corp",
"organizationalUnit": "DevOps",
"expiresAt": "2024-06-30T23:59:59Z",
"createdAt": "2024-01-16T14:20:00Z",
"createdBy": "user-789"
}
],
"pageable": {"pageNumber": 0, "pageSize": 20},
"totalElements": 2,
"totalPages": 1
}

Get a certificate request token by ID

Request example:

curl -s \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/json" \
https://gateway.production.netfoundry.io/frontdoor/3d6d2b6e-6c7a-4a7f-8c3d-9a9d2e1f0b1c/certificate-request-tokens/token-123e4567-e89b-12d3-a456-426614174000

Response example:

{
"id": "token-123e4567-e89b-12d3-a456-426614174000",
"name": "api-service-prod",
"frontdoorId": "3d6d2b6e-6c7a-4a7f-8c3d-9a9d2e1f0b1c",
"token": "crt_1234567890abcdef",
"commonName": "api.example.com",
"organization": "Example Corp",
"organizationalUnit": "API Services",
"expiresAt": "2024-12-31T23:59:59Z",
"createdAt": "2024-01-15T10:30:00Z",
"createdBy": "user-456"
}

Get a certificate request token by token string

Retrieve a token using its token string instead of ID. This is useful for token validation.

Request example:

curl -s \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/json" \
https://gateway.production.netfoundry.io/frontdoor/3d6d2b6e-6c7a-4a7f-8c3d-9a9d2e1f0b1c/certificate-request-tokens/by-token/crt_1234567890abcdef

Response example:

{
"id": "token-123e4567-e89b-12d3-a456-426614174000",
"name": "api-service-prod",
"frontdoorId": "3d6d2b6e-6c7a-4a7f-8c3d-9a9d2e1f0b1c",
"token": "crt_1234567890abcdef",
"commonName": "api.example.com",
"organization": "Example Corp",
"organizationalUnit": "API Services",
"expiresAt": "2024-12-31T23:59:59Z",
"createdAt": "2024-01-15T10:30:00Z",
"createdBy": "user-456"
}

Create a certificate request token

Request example:

curl -s -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"name": "api-service-prod",
"commonName": "api.example.com",
"organization": "Example Corp",
"organizationalUnit": "API Services"
}' \
https://gateway.production.netfoundry.io/frontdoor/3d6d2b6e-6c7a-4a7f-8c3d-9a9d2e1f0b1c/certificate-request-tokens

Successful response example:

{
"id": "token-123e4567-e89b-12d3-a456-426614174000",
"name": "api-service-prod",
"frontdoorId": "3d6d2b6e-6c7a-4a7f-8c3d-9a9d2e1f0b1c",
"token": "crt_1234567890abcdef",
"commonName": "api.example.com",
"organization": "Example Corp",
"organizationalUnit": "API Services",
"expiresAt": "2024-12-31T23:59:59Z",
"createdAt": "2024-01-17T09:15:00Z",
"createdBy": "user-456"
}

Get the enrollment JWT for a certificate request token

Returns the signed artifact to hand to the end user. It carries the endpoint to call, the token, and the certificate subject the token fixes.

Request example:

curl -s \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/json" \
https://gateway.production.netfoundry.io/frontdoor/v2/3d6d2b6e-6c7a-4a7f-8c3d-9a9d2e1f0b1c/certificate-request-tokens/e89b12d3a4564000/enrollment-jwt

Response example:

{
"frontdoorId": "3d6d2b6e-6c7a-4a7f-8c3d-9a9d2e1f0b1c",
"certificateRequestTokenId": "e89b12d3a4564000",
"jwt": "eyJhbGciOiJFUzI1NiIsImtpZCI6ImEzZjkxYzQ3In0.eyJpc3MiOiJmcm9udGRvb3IiLCJhdWQiOiIzZDZkMmI2ZS02YzdhLTRhN2Yt...",
"url": "https://gateway.production.netfoundry.io/frontdoor/v2/3d6d2b6e-6c7a-4a7f-8c3d-9a9d2e1f0b1c/client-certificates/enroll",
"expiresAt": "2024-12-31T23:59:59Z"
}

The JWT claims are iss (always frontdoor), aud (the Frontdoor id), sub (the token), exp, iat, url, name, validYears, and cn, o and ou for whichever subject fields the token sets.

Get the public key set

Verifies an enrollment JWT. This endpoint needs no authentication and only ever returns public keys.

Request example:

curl -s https://gateway.production.netfoundry.io/frontdoor/v2/public/.well-known/jwks.json

Response example:

{
"keys": [
{
"kty": "EC",
"crv": "P-256",
"kid": "a3f91c47",
"use": "sig",
"alg": "ES256",
"x": "f83Kx1_QpV2mR9tLvB4jN1wYcHsQ8aZ3eK5oU2gT6iV",
"y": "0bNxwWq4mJ7cD1yFhP9sX2nR8kL5tA3eG6uY0zC1vB4"
}
]
}

Match the kid in a JWT's header against the key set to select the verifying key, rather than pinning a single key. When the signing key is replaced, the set holds the new key alongside the one it replaced, so a JWT issued earlier still verifies; a superseded key stays published until every JWT it could have signed has expired. Re-read the key set periodically, or whenever a kid is not recognized.

For checking one JWT by hand, see Checking a JWT by hand.

Partial update of a certificate request token

Update specific fields of an existing token.

note

The token string can't be updated.

Request example:

curl -s -X PATCH \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"name": "updated-api-service-token"
}' \
https://gateway.production.netfoundry.io/frontdoor/3d6d2b6e-6c7a-4a7f-8c3d-9a9d2e1f0b1c/certificate-request-tokens/token-123e4567-e89b-12d3-a456-426614174000

Full update of a certificate request token

Replace the entire token definition.

note

The token string can't be updated.

Request example:

curl -s -X PUT \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"name": "fully-updated-token",
"commonName": "updated.example.com",
"organization": "Updated Corp",
"organizationalUnit": "Updated Services"
}' \
https://gateway.production.netfoundry.io/frontdoor/3d6d2b6e-6c7a-4a7f-8c3d-9a9d2e1f0b1c/certificate-request-tokens/token-123e4567-e89b-12d3-a456-426614174000

Delete a certificate request token

Request example:

curl -s -X DELETE \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/json" \
https://gateway.production.netfoundry.io/frontdoor/3d6d2b6e-6c7a-4a7f-8c3d-9a9d2e1f0b1c/certificate-request-tokens/token-123e4567-e89b-12d3-a456-426614174000

Response example:

{
"id": "token-123e4567-e89b-12d3-a456-426614174000",
"name": "api-service-prod",
"frontdoorId": "3d6d2b6e-6c7a-4a7f-8c3d-9a9d2e1f0b1c",
"deletedAt": "2024-01-17T16:45:00Z",
"deletedBy": "user-456"
}

Using tokens to create client certificates

The end user sends the enrollment JWT back with a Certificate Signing Request. The JWT authenticates the request, so no bearer token is needed. The endpoint is the url claim inside the JWT.

Request example:

curl -s -X POST \
-H "Content-Type: application/json" \
-d '{
"jwt": "eyJhbGciOiJFUzI1NiIsImtpZCI6ImEzZjkxYzQ3In0...",
"csr": "-----BEGIN CERTIFICATE REQUEST----- ... -----END CERTIFICATE REQUEST-----"
}' \
https://gateway.production.netfoundry.io/frontdoor/v2/3d6d2b6e-6c7a-4a7f-8c3d-9a9d2e1f0b1c/client-certificates/enroll

The client certificate is created using the metadata from the token (Name, Common Name, Organization, and the validity period). The subject of the CSR must match every subject field the token sets; a request that disagrees returns 400 naming each mismatch. Subject fields the token leaves blank are taken from the CSR. The JWT can be used once, and the token is spent when the certificate is issued.

tip

An end user can verify the JWT against the public key set before sending a CSR, and should check that iss is frontdoor, that aud is the expected Frontdoor, and that url points at an expected host.

Validate an enrollment request

Send the nf-validate header on the enroll request to run the same constraints and get the violations back, without creating a certificate and without spending the enrollment JWT. An empty array means the request would be accepted.

Request example:

curl -s -X POST \
-H "Content-Type: application/json" \
-H "nf-validate: true" \
-d '{
"jwt": "eyJhbGciOiJFUzI1NiIsImtpZCI6ImEzZjkxYzQ3In0...",
"csr": "-----BEGIN CERTIFICATE REQUEST----- ... -----END CERTIFICATE REQUEST-----"
}' \
https://gateway.production.netfoundry.io/frontdoor/v2/3d6d2b6e-6c7a-4a7f-8c3d-9a9d2e1f0b1c/client-certificates/enroll

Response example:

[
{
"path": "$.csr",
"label": "csr",
"message": "CN must be 'api.example.com' but is 'api-gw-02.example.com'"
},
{
"path": "$.csr",
"label": "csr",
"message": "OU must be 'API Services' but is 'Engineering'"
}
]

The subject check runs here, so a mismatch is reported before the real request rather than discovered by it. An enrollment JWT that has expired, been spent, or was issued for a different Frontdoor is reported as a violation on $.jwt; the real request answers that case with 403.

Redeeming with the token string

The older endpoint accepts the bare token string in the path, and takes either a CSR or an already created certificate. It applies no subject constraint. Prefer the enrollment JWT for new integrations.

Request example:

curl -s -X POST \
-H "Content-Type: application/json" \
-d '{
"type": "CSR",
"value": "-----BEGIN CERTIFICATE REQUEST----- ... -----END CERTIFICATE REQUEST-----"
}' \
https://gateway.production.netfoundry.io/frontdoor/v2/3d6d2b6e-6c7a-4a7f-8c3d-9a9d2e1f0b1c/client-certificates/token/k7m2xq9v4t

Creating a client certificate by uploading an already created certificate.

Request example:

curl -s -X POST \
-H "Content-Type: application/json" \
-d '{
"name": "api-service-prod",
"type": "CERTIFICATE",
"value": "-----BEGIN CERTIFICATE----- ... -----END CERTIFICATE -----"
}' \
https://gateway.production.netfoundry.io/frontdoor/v2/3d6d2b6e-6c7a-4a7f-8c3d-9a9d2e1f0b1c/client-certificates/token/k7m2xq9v4t

Token security and lifecycle

Security considerations

  • Enrollment JWTs and token strings are sensitive: Treat them like passwords or API keys
  • Secure distribution: Use secure channels to share them
  • Verifiable origin: An end user can check an enrollment JWT against the public key set before acting on it
  • Regular cleanup: Delete unused or expired tokens; deleting a token revokes its enrollment JWT

Expiration behavior

  • Tokens can't be used after their expiration time
  • Expired tokens return HTTP 401 errors when used
  • Clean up expired tokens to maintain security hygiene

Unique naming

  • Token names must be unique within a Frontdoor
  • Names can't conflict with existing client certificate names
  • Choose descriptive names that indicate the token's purpose

Technical notes

Paths

- GET /frontdoor/v2/\{frontdoorId\}/certificate-request-tokens[?page=0&size=20&sort=name,asc]
- GET /frontdoor/v2/\{frontdoorId\}/certificate-request-tokens/{id}
- GET /frontdoor/v2/\{frontdoorId\}/certificate-request-tokens/{id}/enrollment-jwt
- GET /frontdoor/v2/\{frontdoorId\}/certificate-request-tokens/token/{token}
- POST /frontdoor/v2/\{frontdoorId\}/certificate-request-tokens
- PATCH /frontdoor/v2/\{frontdoorId\}/certificate-request-tokens/{id}
- PUT /frontdoor/v2/\{frontdoorId\}/certificate-request-tokens/{id}
- DELETE /frontdoor/v2/\{frontdoorId\}/certificate-request-tokens/{id}
- POST /frontdoor/v2/\{frontdoorId\}/client-certificates/enroll
- POST /frontdoor/v2/\{frontdoorId\}/client-certificates/enroll (with the `nf-validate` header, to validate only)
- GET /frontdoor/v2/public/.well-known/jwks.json

Required fields

  • name: String, must be unique within Frontdoor both for other tokens and existing client certificates

Optional fields

Each subject field that is set becomes a requirement on the Certificate Signing Request presented at enrollment. A field left blank places no constraint.

  • commonName: String, the Common Name the certificate must use
  • organization: String, the Organization the certificate must use
  • organizationalUnit: String, the Organizational Unit the certificate must use

Common errors

400 Bad Request (client error)

{
"error": "invalid_request",
"message": "Value for <property> must be of <type>"
}

Possible reasons include:

  • Clock skew: If tokens seem instantly expired, check system time synchronization on the machine making requests.
  • Token format: Authorization header must be exactly Authorization: Bearer <token> with a space after Bearer or Authorization: Basic <username:token> with a space after Basic and a color separator.

401 Unauthorized (missing/expired token)

{
"error": "unauthorized",
"message": "Bearer token is missing or invalid"
}

Possible reasons include:

  • Clock skew: If tokens seem instantly expired, check system time synchronization on the machine making requests.
  • Token format: Authorization header must be exactly Authorization: Bearer <token> with a space after Bearer or Authorization: Basic <username:token> with a space after Basic and a color separator.

403 Forbidden (not_authorized)

{
"error": "not_found",
"message": "Frontdoor 3d6d2b6e-6c7a-4a7f-8c3d-9a9d2e1f0b1c not found"
}

Possible reasons include:

  • Invalid token: The token is not valid.
  • The token does not grant access to the requested resource.
  • Clock skew: If tokens seem instantly expired, check system time synchronization on the machine making requests.
  • Token format: Authorization header must be exactly Authorization: Bearer <token> with a space after Bearer or Authorization: Basic <username:token> with a space after Basic and a color separator.