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
- The examples assume you have a valid bearer token. If you need help obtaining a token, see Authentication for NetFoundry REST APIs.
- Base URL for all API calls is https://gateway.production.netfoundry.io/frontdoor.
- Expected headers for all API calls:
Authorization: Bearer YOUR_ACCESS_TOKENAccept: application/hal+json or application/json
- 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.
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.
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.
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:
Authorizationheader must be exactlyAuthorization: Bearer <token>with a space after Bearer orAuthorization: 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:
Authorizationheader must be exactlyAuthorization: Bearer <token>with a space after Bearer orAuthorization: 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:
Authorizationheader must be exactlyAuthorization: Bearer <token>with a space after Bearer orAuthorization: Basic <username:token>with a space after Basic and a color separator.