stemp Logostemp Developer

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

  1. The organization initiates the app installation from the stemp console or your install URL.
  2. Your app redirects the user to the stemp authorization endpoint.
  3. The user sees a consent screen and grants permission.
  4. stemp redirects back to your app with an authorization code.
  5. Your app exchanges the code for access and refresh tokens.

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=S256

Parameters

ParameterRequiredDescription
response_typeYesMust be code
client_idYesYour application's client ID
redirect_uriYesMust match a registered redirect URI
scopeYesSpace-separated list of scopes (e.g., pass:read pass:create)
stateRecommendedRandom string for CSRF protection
code_challengeRecommendedPKCE code challenge (base64url-encoded SHA-256 hash)
code_challenge_methodRecommendedS256 (recommended) or plain

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_STATE

Always 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_STATE

Step 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"
}
ParameterRequiredDescription
codeYesThe authorization code from the callback
redirect_uriYesMust match the URI used in the authorization request
code_verifierConditionalRequired 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"
}
FieldDescription
access_tokenJWT access token (RS256 signed, valid for 30 minutes)
token_typeAlways bearer
expires_inToken lifetime in seconds (1800 = 30 minutes)
refresh_tokenOpaque refresh token (valid for 30 days)
scopeSpace-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=STATE

Token Exchange Errors

ErrorDescription
invalid_requestMissing or invalid parameters
invalid_grantAuthorization code is expired, already used, or invalid
invalid_scopeRequested 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 state parameter — 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 401 response.
  • Use HTTPS redirect URIs — stemp validates redirect URIs must be exact matches.