Skip to main content

API Authentication

JJHub supports two authentication modes:
  • Session authentication for browser-based login flows
  • Bearer token authentication for CLI, CI, and other programmatic clients

Session Authentication

Session-based login flows establish authentication with cookies instead of a bearer token. Successful session login responses set:
  • jjhub_session for the authenticated session
  • __csrf for CSRF protection on session-authenticated write requests

Sign in with Key

Sign in with Key uses a challenge-response flow:
  1. GET /auth/key/nonce
  2. Sign a structured message with the returned nonce
  3. POST /auth/key/verify
  4. Receive a JJHub session via Set-Cookie

GET /auth/key/nonce

Returns a single-use nonce for the next signature attempt.
curl https://api.jjhub.tech/auth/key/nonce
{
  "nonce": "abc123"
}
Notes:
  • Nonces are single-use
  • Nonces expire after about 10 minutes

POST /auth/key/verify

Submit a signed message and signature to create a JJHub session. The signed message must include the configured JJHub auth domain on the first line and the server-issued nonce. On hosted JJHub, the message domain is jjhub.tech even though the API endpoint host is api.jjhub.tech.
jjhub.tech wants you to sign in with your key:
0x71C7656EC7ab88b098defB751B7401B5f6d8976F

Sign in to JJHub

URI: https://jjhub.tech
Version: 1
Chain ID: 1
Nonce: abc123
Issued At: 2026-03-07T12:00:00Z
curl -X POST https://api.jjhub.tech/auth/key/verify \
  -H "Content-Type: application/json" \
  -d @- <<'JSON'
{
  "message": "jjhub.tech wants you to sign in with your key:\n0x71C7656EC7ab88b098defB751B7401B5f6d8976F\n\nSign in to JJHub\n\nURI: https://jjhub.tech\nVersion: 1\nChain ID: 1\nNonce: abc123\nIssued At: 2026-03-07T12:00:00Z",
  "signature": "0x5f2c...9ab1"
}
JSON
{
  "user": {
    "id": 42,
    "username": "alice"
  }
}
A successful response also includes Set-Cookie headers for jjhub_session and __csrf. Common failure modes:
  • 400 invalid JSON or missing message / signature
  • 401 invalid signature or invalid / expired nonce
  • 403 account suspended or not permitted by closed-alpha access controls
  • 415 request body is not application/json

GitHub OAuth

JJHub also supports browser-based login via GitHub OAuth.

GET /auth/github

Starts the OAuth flow, stores a CSRF state verifier in a cookie, and redirects the browser to GitHub.
curl -i https://api.jjhub.tech/auth/github
HTTP/2 302 Found
Set-Cookie: jjhub_oauth_state=8d3f...; Path=/; HttpOnly; Secure; SameSite=Lax
Location: https://github.com/login/oauth/authorize?client_id=...&state=...&scope=read%3Auser+user%3Aemail
Notes:
  • The response is a redirect, not JSON
  • The jjhub_oauth_state cookie must be preserved through the callback request

GET /auth/github/callback

GitHub redirects the browser back to JJHub with code and state query parameters:
curl -i "https://api.jjhub.tech/auth/github/callback?code=abc123&state=xyz789" \
  -H "Cookie: jjhub_oauth_state=8d3f..."
HTTP/2 302 Found
Set-Cookie: jjhub_session=3f1f...; Path=/; HttpOnly; Secure; SameSite=Strict
Set-Cookie: __csrf=8bf1...; Path=/; Secure; SameSite=Strict
Set-Cookie: jjhub_oauth_state=; Path=/; Max-Age=0; HttpOnly; Secure; SameSite=Lax
Location: /
Notes:
  • The callback returns a redirect after creating the session
  • On success, JJHub clears the temporary jjhub_oauth_state cookie
  • The default post-login redirect is /
Common failure modes:
  • 400 missing code or state, or GitHub code exchange failed
  • 401 invalid or replayed OAuth state
  • 403 account suspended or not permitted by closed-alpha access controls
  • 409 GitHub account email is already in use by another JJHub account

Bearer Tokens

Programmatic API clients can authenticate with a bearer token:
Authorization: Bearer jjhub_your_token_here

Generating Tokens

Via CLI:
jjhub auth token create --name "My Token" --scopes "read:repository,write:repository"
Via API:
curl -X POST https://api.jjhub.tech/tokens \
  -H "Authorization: Bearer jjhub_existing_token" \
  -H "Content-Type: application/json" \
  -d '{"name": "My Token", "scopes": ["read:repository", "write:repository"]}'

Token Format

All tokens are prefixed with jjhub_ and are SHA-256 hashed before storage. Tokens are shown once at creation time.

Error Responses

{
  "message": "Unauthorized",
  "errors": []
}
HTTP 401 is returned for missing or invalid tokens. HTTP 403 is returned for insufficient scope.