OAuth2 Flow
Authorization Code flow with PKCE for stemp API access.
OAuth2 Authorization Code Flow with PKCE
stemp uses the Authorization Code flow with Proof Key for Code Exchange (PKCE) for secure app authentication. This flow allows third-party apps to act on behalf of an organization.
Flow Overview
- The organization initiates the app installation from the stemp console or your install URL.
- Your app redirects the user to the stemp authorization endpoint.
- The user sees a consent screen and grants permission.
- stemp redirects back to your app with an authorization code.
- Your app exchanges the code for access and refresh tokens.
Step 1: Generate PKCE Parameters (Recommended)
PKCE is recommended for all clients. Generate a random code_verifier (43–128 characters) and derive the code_challenge:
import { randomBytes, createHash } from 'crypto'
const codeVerifier = randomBytes(32).toString('base64url')
const codeChallenge = createHash('sha256')
.update(codeVerifier)
.digest('base64url')Step 2: Authorization Request
Redirect the user to the stemp authorization endpoint:
GET https://api.stemp.app/api/v1/apps/oauth2/authorize
?response_type=code
&client_id=YOUR_CLIENT_ID
&redirect_uri=https://yourapp.com/callback
&scope=pass:read pass:create loyalty:manage
&state=RANDOM_STATE
&code_challenge=CODE_CHALLENGE
&code_challenge_method=S256Parameters
| Parameter | Required | Description |
|---|---|---|
response_type | Yes | Must be code |
client_id | Yes | Your application's client ID |
redirect_uri | Yes | Must match a registered redirect URI |
scope | Yes | Space-separated list of scopes (e.g., pass:read pass:create) |
state | Recommended | Random string for CSRF protection |
code_challenge | Recommended | PKCE code challenge (base64url-encoded SHA-256 hash) |
code_challenge_method | Recommended | S256 (recommended) or plain |
Consent Screen
The user is shown a consent screen listing the scopes your app requests. They can either grant or deny access. If the app is already installed, the consent screen shows any new scopes being requested.
Step 3: Handle the Callback
After the user authorizes, they are redirected to your redirect_uri:
https://yourapp.com/callback?code=AUTHORIZATION_CODE&state=RANDOM_STATEAlways verify the state parameter matches what you sent to prevent CSRF attacks.
If the user denies the request, you receive an error:
https://yourapp.com/callback?error=access_denied&state=RANDOM_STATEStep 4: Exchange Code for Tokens
POST https://api.stemp.app/api/v1/apps/oauth2/token
Content-Type: application/json
{
"code": "AUTHORIZATION_CODE",
"redirect_uri": "https://yourapp.com/callback",
"code_verifier": "CODE_VERIFIER"
}| Parameter | Required | Description |
|---|---|---|
code | Yes | The authorization code from the callback |
redirect_uri | Yes | Must match the URI used in the authorization request |
code_verifier | Conditional | Required if code_challenge was provided in Step 2 |
Note: Unlike some OAuth2 implementations, stemp does not require client_secret in the token exchange. Authentication is handled through the authorization code and optional PKCE verification.
Response
{
"access_token": "eyJhbGciOiJSUzI1NiJ9...",
"token_type": "bearer",
"expires_in": 1800,
"refresh_token": "srt_dGhpcyBpcyBhIHJl...",
"scope": "pass:read pass:create loyalty:manage"
}| Field | Description |
|---|---|
access_token | JWT access token (RS256 signed, valid for 30 minutes) |
token_type | Always bearer |
expires_in | Token lifetime in seconds (1800 = 30 minutes) |
refresh_token | Opaque refresh token (valid for 30 days) |
scope | Space-separated list of granted scopes |
Refreshing Tokens
When the access token expires, use the refresh token to get a new one:
POST https://api.stemp.app/api/v1/apps/oauth2/refresh
Content-Type: application/json
{
"refresh_token": "srt_dGhpcyBpcyBhIHJl..."
}The endpoint also accepts application/x-www-form-urlencoded:
curl -X POST https://api.stemp.app/api/v1/apps/oauth2/refresh \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "refresh_token=srt_dGhpcyBpcyBhIHJl..."Response
{
"access_token": "eyJhbGciOiJSUzI1NiJ9...",
"token_type": "bearer",
"expires_in": 1800,
"scope": "pass:read pass:create loyalty:manage",
"refresh_token": "srt_bmV3IHJlZnJlc2g..."
}The refresh_token field may contain a new refresh token if the current one is close to expiry (within 7 days). If refresh_token is null, continue using your existing refresh token.
Error Handling
Authorization Errors
If the authorization request fails, the user is redirected to your redirect_uri with error parameters:
https://yourapp.com/callback?error=ERROR_CODE&error_description=DESCRIPTION&state=STATEToken Exchange Errors
| Error | Description |
|---|---|
invalid_request | Missing or invalid parameters |
invalid_grant | Authorization code is expired, already used, or invalid |
invalid_scope | Requested scope is not allowed for this app |
Token Error Response
{
"error": "invalid_grant",
"error_description": "Authorization code is expired or already used"
}Security Considerations
- Always use PKCE — even though it's optional, PKCE prevents authorization code interception attacks.
- Validate the
stateparameter — compare it to the value you stored before the redirect. - Store tokens securely — never expose tokens in client-side code, URLs, or logs.
- Handle token refresh — implement automatic refresh before or when you receive a
401response. - Use HTTPS redirect URIs — stemp validates redirect URIs must be exact matches.