client_secret is compromised. Most OAuth client libraries (openid-client for Node, Authlib for Python, IdentityModel.OidcClient for .NET, etc.) support PKCE with a single configuration flag.API Access Request and a brief description of your use case.code_verifier — a cryptographically random string, 43–128 characters from the URL-safe alphabet (A-Z, a-z, 0-9, -, ., _, ~). Store it in the user's session, keyed by state, so you can retrieve it during the token exchange.code_challenge — BASE64URL(SHA256(code_verifier)). This is sent in the authorize request; the raw verifier never leaves your server until step 3.= padding) — not standard Base64.https://auth.navexa.io/authorize?
response_type=code&
client_id=YOUR_CLIENT_ID&
redirect_uri=YOUR_REDIRECT_URI&
audience=https://api.navexa.io/api&
scope=openid%20profile%20email%20offline_access&
state=RANDOM_STRING&
code_challenge=YOUR_CODE_CHALLENGE&
code_challenge_method=S256| Parameter | Required | Description |
|---|---|---|
response_type | Yes | Must be code. |
client_id | Yes | The Client ID issued to your application. |
redirect_uri | Yes | Must exactly match a redirect URI registered for your application. |
audience | Yes | Must be https://api.navexa.io/api. This is the Auth0 identifier for the Navexa API — not a URL you call. (The actual API base URL is https://api.navexa.com.au/api.) Without this parameter, the access token will not be accepted by the API and calls will return 401 Unauthorized. |
scope | Yes | Space-separated list — see Scopes. Must include offline_access if you want a refresh token. |
state | Recommended | A random, unguessable string. Verify it matches when the user is redirected back, to prevent CSRF. |
code_challenge | Yes | The PKCE challenge from Step 1. |
code_challenge_method | Yes | Must be S256. The plain method is not supported. |
redirect_uri with code and state query parameters.code_verifier you stored in the user's session in Step 1, then send it alongside the rest of the token request:curl --request POST \
--url https://auth.navexa.io/oauth/token \
--header 'content-type: application/json' \
--data '{
"grant_type": "authorization_code",
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET",
"code": "AUTHORIZATION_CODE",
"redirect_uri": "YOUR_REDIRECT_URI",
"code_verifier": "YOUR_CODE_VERIFIER"
}'code_verifier must be the original random string from Step 1 — not the hashed challenge. Auth0 hashes the verifier itself and compares it to the stored challenge.{
"access_token": "eyJhbGciOi...",
"refresh_token": "v1.Mr...",
"id_token": "eyJhbGciOi...",
"token_type": "Bearer",
"expires_in": 86400,
"scope": "openid profile email offline_access"
}refresh_token is only returned if offline_access was requested. id_token is only returned if openid was requested.https://api.navexa.com.au/api. Send the access token in the Authorization header:curl --request GET \
--url https://api.navexa.com.au/api/some-endpoint \
--header "Authorization: Bearer YOUR_ACCESS_TOKEN"offline_access you will have a refresh token — use it to get a new access token without user interaction:curl --request POST \
--url https://auth.navexa.io/oauth/token \
--header 'content-type: application/json' \
--data '{
"grant_type": "refresh_token",
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET",
"refresh_token": "YOUR_REFRESH_TOKEN"
}'invalid_grant and the user will need to re-authorize via Step 2.| Scope | Purpose |
|---|---|
openid | Required for OpenID Connect — returns an id_token. |
profile | Basic profile claims (name, etc.) on the id_token. |
email | Email and email-verified claims on the id_token. |
offline_access | Issues a refresh_token so you can refresh access tokens without user interaction. |
scope=openid profile email offline_access.{
"error": "invalid_grant",
"error_description": "Authorization code expired"
}invalid_request, invalid_client, invalid_grant, unauthorized_client, unsupported_grant_type, invalid_scope, access_denied.invalid_request — "The PKCE protocol extension is required." (no code_challenge was sent on /authorize).invalid_grant — "Failed to verify code_verifier." (the verifier sent on /oauth/token doesn't match the challenge from /authorize).client_secret. Always send code_challenge + code_challenge_method=S256 on /authorize and code_verifier on /oauth/token.audience=https://api.navexa.io/api on /authorize requests, otherwise the access token will not be accepted by the Navexa API.client_secret on the server. Native and single-page apps that cannot store a secret should still use this flow with PKCE, simply omitting client_secret from the token request.state on the redirect back to your redirect_uri to prevent CSRF.x-api-key header:curl --request GET \
--url https://api.navexa.com.au/api/some-endpoint \
--header "x-api-key: YOUR_API_KEY"