Skip to main content

OAuth2 PKCE

Makenot.work supports OAuth2 Authorization Code with PKCE for “Log in with Makenot.work” flows. This lets third-party applications authenticate users without handling their passwords directly.

Overview

  1. Your app generates a PKCE code verifier and challenge
  2. User is redirected to makenot.work/oauth/authorize to log in and consent
  3. Makenot.work redirects back with an authorization code
  4. Your app exchanges the code for a JWT access token
  5. Use the token to call SyncKit or userinfo endpoints

Client Registration

Your OAuth client ID is the API key of your SyncKit app. Create a SyncKit app from the Makenot.work dashboard to get one.

Redirect URIs

Localhost: http://127.0.0.1:{port}/... and http://localhost:{port}/... are always allowed without registration. Use these for desktop apps.

Remote: Non-localhost redirect URIs must be registered on your SyncKit app. Contact support to add them.

Authorization Request

Redirect the user to the authorize endpoint:

GET /oauth/authorize
  ?response_type=code
  &client_id=<your-api-key>
  &redirect_uri=http://127.0.0.1:8765/callback
  &state=<random-string>
  &code_challenge=<S256-challenge>
  &code_challenge_method=S256
ParameterRequiredDescription
response_typeYesMust be code
client_idYesYour SyncKit app API key
redirect_uriYesWhere to send the authorization code
stateYesRandom string to prevent CSRF; verify it in the callback
code_challengeYesBase64url-encoded SHA-256 hash of the code verifier
code_challenge_methodYesMust be S256

The user sees a consent page. After logging in and approving, they are redirected to:

{redirect_uri}?code=<authorization-code>&state=<your-state>

Token Exchange

Exchange the authorization code for an access token:

POST /oauth/token
Content-Type: application/json

{
  "grant_type": "authorization_code",
  "code": "<authorization-code>",
  "redirect_uri": "http://127.0.0.1:8765/callback",
  "code_verifier": "<original-code-verifier>",
  "client_id": "<your-api-key>"
}

Response:

{
  "access_token": "eyJ...",
  "token_type": "Bearer",
  "expires_in": 604800,
  "user_id": "550e8400-...",
  "app_id": "660f9500-..."
}

The authorization code is single-use. The server verifies SHA256(code_verifier) == code_challenge before issuing a token.

User Info

Retrieve the authenticated user’s profile:

GET /oauth/userinfo
Authorization: Bearer <access_token>

Response:

{
  "user_id": "550e8400-...",
  "username": "alice",
  "display_name": "Alice",
  "avatar_url": "https://makenot.work/static/avatars/alice.jpg"
}

PKCE Implementation

PKCE prevents authorization code interception:

  1. Generate a random code verifier (43-128 characters, URL-safe)
  2. Compute code_challenge = BASE64URL(SHA256(code_verifier))
  3. Send code_challenge in the authorization request
  4. Send code_verifier in the token exchange

The server rejects token requests where the verifier does not match the challenge.

Example (Rust)

use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
use sha2::{Digest, Sha256};

let verifier: String = (0..64)
    .map(|_| rand::random::<u8>())
    .map(|b| format!("{:02x}", b))
    .collect();

let challenge = URL_SAFE_NO_PAD.encode(Sha256::digest(verifier.as_bytes()));

Token Usage

The access token works with all SyncKit endpoints:

  • Cloud Sync: push/pull data, manage devices
  • OTA Updates: manage releases and artifacts
  • User info (above)

Tokens expire after 7 days. After expiration, redirect the user through the authorization flow again.

Error Handling

Authorization errors redirect to redirect_uri with an error parameter:

{redirect_uri}?error=access_denied&state=<your-state>

Token exchange errors return JSON:

{
  "error": "invalid_grant"
}
ErrorMeaning
access_deniedUser denied consent
invalid_clientUnknown client_id
invalid_grantCode expired, already used, or verifier mismatch
invalid_requestMissing required parameters

See Also