Authorization Code Flow
Step-by-step walkthrough of Vacation Tracker's OAuth2 Authorization Code Grant flow with PKCE.
The Authorization Code Grant is the only supported OAuth2 flow. It works for both public clients (desktop, mobile, or browser apps that can't keep a secret) and confidential clients (server-side apps). PKCE is required for public clients, and optional but recommended for confidential ones.
Flow diagram
Step 0 (public clients only): Register via DCR
Public clients like desktop apps, CLI tools, and MCP clients register themselves on first use. One request returns a client_id you can reuse forever:
curl -X POST https://auth.vacationtracker.io/oauth/register \
-H "Content-Type: application/json" \
-d '{
"client_name": "My Desktop App",
"redirect_uris": ["http://127.0.0.1:53126/callback"]
}'Response:
{
"client_id": "6a8cd456-...",
"client_name": "My Desktop App",
"redirect_uris": ["http://127.0.0.1:53126/callback"],
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"],
"token_endpoint_auth_method": "none"
}Confidential clients skip this step. Your client_id and client_secret are issued manually by the Vacation Tracker team.
See Endpoints → POST /oauth/register for the full request/response contract and rate limits.
Step 1: Generate a PKCE pair and redirect to authorize
Before sending the user to the authorization endpoint, generate a code verifier (43–128 URL-safe random characters) and derive a code challenge from it using SHA-256 plus base64url.
code_verifier=$(openssl rand 64 | openssl base64 -A | tr -d '=' | tr '+/' '-_')
code_challenge=$(printf '%s' "$code_verifier" \
| openssl dgst -binary -sha256 \
| openssl base64 -A | tr -d '=' | tr '+/' '-_')All three produce an 86-character verifier from 64 bytes (512 bits) of entropy. That's well above RFC 7636's 256-bit recommendation and under the 128-character cap. The challenge is the base64url-encoded SHA-256 of the verifier.
Store the code_verifier server-side (or in platform secure storage) keyed by your state value. You'll need it at the token exchange step.
Redirect the user's browser to:
GET https://auth.vacationtracker.io/oauth/authorize
?client_id=your-client-id
&redirect_uri=https://yourapp.com/callback
&state=random-csrf-token
&scope=openid email profile
&response_type=code
&code_challenge=<sha256-base64url>
&code_challenge_method=S256| Parameter | Required | Description |
|---|---|---|
client_id | Yes | Your registered OAuth client ID (from DCR or manual registration) |
redirect_uri | Yes | Must exactly match a registered redirect URI |
state | Yes | Opaque CSRF token. Verify unchanged in the callback. |
scope | No | Space-separated. Defaults to openid. |
response_type | No | Must be code. Defaults to code. |
code_challenge | Required for public clients, optional for confidential | Base64url-encoded SHA-256 hash of your verifier |
code_challenge_method | Required if code_challenge is present | Must be S256 |
Step 2: User authenticates
Vacation Tracker redirects the user to its login page. The user authenticates with:
- Microsoft
- Slack
- Email + password
Step 3: User grants permission
After login, the user sees the consent screen with your application name and the requested scopes. They approve or deny.
Step 4: Receive authorization code
On approval, Vacation Tracker redirects the browser back to your redirect_uri with a code and the state you sent:
https://yourapp.com/callback?code=a1b2c3d4-e5f6-7890-abcd-ef1234567890&state=random-csrf-token- Verify that
statematches the value you sent in Step 1. - The authorization code is single-use and expires after 10 minutes.
Step 5: Exchange code for tokens
Look up the code_verifier you stored in Step 1, then POST to /oauth/token.
Public client (PKCE, no secret):
curl -X POST https://auth.vacationtracker.io/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code" \
-d "code=a1b2c3d4-..." \
-d "redirect_uri=https://yourapp.com/callback" \
-d "client_id=your-client-id" \
-d "code_verifier=<your-stored-verifier>"Confidential client (client_secret; PKCE optional):
curl -X POST https://auth.vacationtracker.io/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-u "your-client-id:your-client-secret" \
-d "grant_type=authorization_code" \
-d "code=a1b2c3d4-..." \
-d "redirect_uri=https://yourapp.com/callback" \
-d "code_verifier=<your-stored-verifier>"code_verifier is required if Step 1 included a code_challenge, regardless of client type.
Step 6: Receive tokens
On success, you receive:
{
"access_token": "eyJ...",
"id_token": "eyJ...",
"refresh_token": "eyJ...",
"token_type": "Bearer",
"expires_in": 3600
}access_token: used as the Bearer for API requests. 60-minute lifetime.id_token: OpenID Connect identity claims. 60-minute lifetime. For MCP, the Bearer you actually send is theid_token. See the MCP setup guide.refresh_token: used to get new tokens. 365-day lifetime.
Step 7: Use the access token
curl https://auth.vacationtracker.io/oauth/me \
-H "Authorization: Bearer eyJ..."Refreshing tokens
When the access token expires, use the refresh token to get a new one. Confidential clients add client_secret (or Basic auth); public clients send only client_id.
curl -X POST https://auth.vacationtracker.io/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=refresh_token" \
-d "refresh_token=eyJ..." \
-d "client_id=your-client-id"The same refresh token is echoed back. Vacation Tracker does not rotate refresh tokens.
Metadata discovery
If your OAuth client library supports RFC 8414, point it at:
https://auth.vacationtracker.io/.well-known/oauth-authorization-serverThe response advertises every endpoint and capability (supported grant types, PKCE methods, registration_endpoint, and so on), so the library can configure itself without hard-coded URLs.
Error handling
Both endpoints return errors as JSON:
{
"error": "invalid_grant",
"error_description": "Code verifier did not match the challenge"
}See Endpoints for the complete list of error codes per endpoint.