OAuth2 Authentication

Snapbooks API uses OAuth2 for secure authentication and authorization. This provides a robust framework for secure API access while allowing fine-grained control over permissions.

Overview

OAuth2 is an industry-standard protocol that allows secure API authorization in a simple and standard way. Snapbooks supports three OAuth2 grant types:

  1. Authorization Code Grant - For applications that can securely store client secrets (web applications)
  2. Authorization Code with PKCE - For public clients that cannot securely store secrets (mobile/single-page apps)
  3. Client Credentials Grant - For server-to-server API access

OAuth2 Endpoints

Endpoint Description
POST /v2/oauth/register Self-service dynamic client registration (RFC 7591, no authentication required)
POST /v2/oauth/clients Register a new OAuth client application (authenticated)
GET /v2/oauth/clients List registered OAuth clients
DELETE /v2/oauth/clients/{client_id} Delete an OAuth client
GET /v2/oauth/authorize Authorization endpoint to obtain user consent
POST /v2/oauth/token Token endpoint to obtain access and refresh tokens
POST /v2/oauth/revoke Revocation endpoint to invalidate tokens

Scopes

The API supports two scopes:

Scope Description
read Read access to the data the authorizing user can see
write Create and change data (write implies read)

Enforcement is on the read/write dimension: a token without the write scope cannot perform mutating requests (POST, PUT, PATCH, DELETE), and a token with no scopes at all cannot access the API. If you register or authorize without requesting a scope, read is granted by default.

Legacy granular scope names from earlier versions of this API (read:profile, write:profile, read:accounting, write:accounting) are still accepted as deprecated aliases and map onto read/write. Don’t use them in new integrations.

Client Registration

There are three ways to get client credentials:

  1. Snapbooks web app — click your avatar and choose API access to create and manage API clients in the UI (also reachable from Settings → Apps & integrations). The client secret is shown once at creation.
  2. Dynamic client registrationPOST /v2/oauth/register (no authentication required, RFC 7591). Limited to user-delegated flows (authorization_code + refresh_token); the client_credentials grant is not available to self-registered clients.
  3. Authenticated registrationPOST /v2/oauth/clients with a Bearer token from a logged-in Snapbooks session (the same token the web app uses). This is the only way to register a client for the server-to-server client_credentials grant.

Self-service registration (RFC 7591)

POST /v2/oauth/register
Content-Type: application/json

{
  "client_name": "My Accounting App",
  "redirect_uris": ["https://myapp.example.com/callback"],
  "grant_types": ["authorization_code", "refresh_token"],
  "scope": "read write"
}

Response:

{
  "client_id": "<client-id>",
  "client_secret": "<client-secret>",
  "client_name": "My Accounting App",
  "redirect_uris": ["https://myapp.example.com/callback"],
  "grant_types": ["authorization_code", "refresh_token"],
  "response_types": ["code"],
  "scope": "read write",
  "token_endpoint_auth_method": "client_secret_post",
  "client_id_issued_at": 1712754455,
  "client_secret_expires_at": 0
}

Register a new client (authenticated)

{your-session-token} is the Bearer token of a logged-in Snapbooks user — the token your browser or mobile session uses. The registered client belongs to that user, and tokens issued to it act on that user’s client accounts.

POST /v2/oauth/clients
Authorization: Bearer {your-session-token}
Content-Type: application/json

{
  "client_name": "My Accounting App",
  "redirect_uris": "https://myapp.example.com/callback",
  "grant_types": "authorization_code refresh_token",
  "scope": "read write"
}

Response:

{
  "client_id": "<client-id>",
  "client_secret": "<client-secret>",
  "client_name": "My Accounting App",
  "redirect_uris": "https://myapp.example.com/callback",
  "grant_types": "authorization_code refresh_token",
  "scope": "read write"
}

Important: Store the client_id and client_secret securely - the secret will not be retrievable later.

List registered clients

GET /v2/oauth/clients
Authorization: Bearer {your-session-token}

Response:

{
  "clients": [
    {
      "client_id": "<client-id>",
      "client_name": "My Accounting App",
      "redirect_uris": "https://myapp.example.com/callback",
      "grant_types": "authorization_code refresh_token",
      "scope": "read write",
      "created_at": "2025-04-10T14:27:35.123456"
    }
  ]
}

Delete a client

DELETE /v2/oauth/clients/{client_id}
Authorization: Bearer {your-session-token}

Response:

{
  "status": "success",
  "message": "Client deleted"
}

Authorization Code Flow

This flow is suitable for web applications that can securely store client secrets.

1. Request Authorization Code

GET /v2/oauth/authorize?
  response_type=code&
  client_id={client_id}&
  redirect_uri=https%3A%2F%2Fmyapp.example.com%2Fcallback&
  scope=read%20write&
  state={random_state_value}

Parameters:

  • response_type: Must be code
  • client_id: Your application’s client ID
  • redirect_uri: URL-encoded callback URL (must match a pre-registered value)
  • scope: Space-separated list of requested permissions
  • state: Random string to prevent CSRF attacks

The user will be prompted to log in (if not already) and authorize your application’s access.

2. Exchange Code for Tokens

After the user authorizes access, they’re redirected to your application:

https://myapp.example.com/callback?code={authorization_code}&state={random_state_value}

Verify that the state matches the one you generated, then exchange the code for tokens:

POST /v2/oauth/token
Content-Type: application/x-www-form-urlencoded
Authorization: Basic {base64(client_id:client_secret)}

grant_type=authorization_code&
code={authorization_code}&
redirect_uri=https%3A%2F%2Fmyapp.example.com%2Fcallback

Response:

{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "def50200fe37bb71b1baba9b5f2d5...",
  "scope": "read write"
}

Authorization Code with PKCE

For public clients (like mobile/SPA apps) that can’t securely store secrets, use PKCE (Proof Key for Code Exchange):

1. Generate a Code Verifier and Challenge

// Generate a random code verifier
const codeVerifier = generateRandomString(64);

// Create the code challenge using S256 method
const codeChallenge = base64UrlEncode(sha256(codeVerifier));

2. Request Authorization Code with PKCE

GET /v2/oauth/authorize?
  response_type=code&
  client_id={client_id}&
  redirect_uri=https%3A%2F%2Fmyapp.example.com%2Fcallback&
  scope=read%20write&
  state={random_state_value}&
  code_challenge={code_challenge}&
  code_challenge_method=S256

3. Exchange Code for Tokens with PKCE

POST /v2/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&
client_id={client_id}&
code={authorization_code}&
redirect_uri=https%3A%2F%2Fmyapp.example.com%2Fcallback&
code_verifier={code_verifier}

Client Credentials Flow

For server-to-server authentication without user involvement:

POST /v2/oauth/token
Content-Type: application/x-www-form-urlencoded
Authorization: Basic {base64(client_id:client_secret)}

grant_type=client_credentials&
scope=read%20write

Response:

{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "read write"
}

Using Refresh Tokens

Access tokens expire after 1 hour. Use a refresh token to get a new access token:

POST /v2/oauth/token
Content-Type: application/x-www-form-urlencoded
Authorization: Basic {base64(client_id:client_secret)}

grant_type=refresh_token&
refresh_token={refresh_token}

Response:

{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "def50200ca01d5cf123b6ba9c5f2a...", // New refresh token
  "scope": "read write"
}

Note: Snapbooks uses token rotation for security. Each time you use a refresh token, you’ll receive a new one, and the old one becomes invalid.

Revoking Tokens

When a user logs out or you no longer need access:

POST /v2/oauth/revoke
Content-Type: application/x-www-form-urlencoded
Authorization: Basic {base64(client_id:client_secret)}

token={refresh_token}&
token_type_hint=refresh_token

Making Authenticated Requests

Use the access token for all API requests:

GET /v2/bank-accounts?client_account_id={client_account_id}
Authorization: Bearer {access_token}

Error Responses

OAuth2 endpoints return standard error responses:

Error Description
invalid_request The request is missing a required parameter
invalid_client Client authentication failed
invalid_grant The provided authorization code or refresh token is invalid
unauthorized_client The client is not authorized to use the requested grant type
unsupported_grant_type The requested grant type is not supported
invalid_scope The requested scope is invalid or unknown
access_denied The user denied the authorization request

Example error response:

{
  "error": "invalid_grant",
  "error_description": "Refresh token expired"
}

Rate Limits

The authentication endpoints are rate-limited per client IP:

Endpoint Limit
POST /v2/oauth/token 60 per minute
POST /v2/oauth/revoke 60 per minute
GET /v2/oauth/authorize 120 per hour
POST /v2/oauth/login 60 per hour
POST /v2/oauth/register 10 per hour

Exceeding a limit returns 429 Too Many Requests. Cache access tokens for their full lifetime (1 hour) instead of requesting a new token per API call.

Security Best Practices

  1. Store tokens securely:
    • Never store tokens in localStorage in browser apps
    • Use secure HTTP-only cookies or secure storage mechanisms
    • For mobile apps, use the platform’s secure storage (Keychain/Keystore)
  2. Implement proper PKCE for public clients (mobile/SPA apps)

  3. Always validate state parameters to prevent CSRF attacks

  4. Request minimal scopes - only ask for the permissions your app needs

  5. Implement token management:
    • Handle token expiration gracefully
    • Revoke tokens when a user logs out
    • Implement proper error handling for authentication failures
  6. Norwegian regulatory compliance:
    • Ensure GDPR compliance with proper user consent
    • Follow Datatilsynet guidelines for storing personal data
    • Implement appropriate logging for audit purposes according to Norwegian bookkeeping regulations

Discovery Endpoints

Snapbooks provides OAuth 2.0 discovery endpoints for automatic client configuration:

  • GET /.well-known/oauth-authorization-server — authorization server metadata (RFC 8414)
  • GET /.well-known/oauth-protected-resource — protected resource metadata (RFC 9728)

These let OAuth clients discover endpoint URLs, supported grant types, and scopes without hard-coding them. See OAuth Discovery for full details.

  • OAuth Clients — register and manage OAuth client applications
  • OAuth Admin — admin endpoints for listing and revoking tokens
  • OAuth Discovery — authorization server and protected resource metadata
  • Signup — ID-porten verified user registration