Authentication

The HOLE Foundation API uses Auth0 for authentication, providing enterprise-grade security with OAuth 2.0 / OpenID Connect (OIDC) support.

Overview

All protected endpoints require a valid JWT Bearer token in the Authorization header:

$Authorization: Bearer YOUR_JWT_TOKEN

Getting Started

1. Get Your Credentials

Contact us at api@theholefoundation.org to receive:

  • Auth0 Domain
  • Client ID
  • Client Secret (for server-side applications)
  • API Audience

2. Choose Authentication Method

We support three authentication flows:

MethodUse CaseBest For
OAuth 2.0Web applications with user loginFrontend apps
M2M (Client Credentials)Server-to-server API callsBackend services, scripts
API KeysSimple authenticationDevelopment, testing

OAuth 2.0 Flow (User Authentication)

Best for web applications where users log in with their credentials.

Step 1: Redirect to Auth0

1const auth0Domain = 'dev-4fszoklachwdh46m.us.auth0.com';
2const clientId = 'YOUR_CLIENT_ID';
3const redirectUri = 'https://yourapp.com/callback';
4const audience = 'https://api.theholefoundation.org';
5
6const authUrl = `https://${auth0Domain}/authorize?` +
7 `response_type=code&` +
8 `client_id=${clientId}&` +
9 `redirect_uri=${redirectUri}&` +
10 `scope=openid profile email&` +
11 `audience=${audience}`;
12
13// Redirect user to authUrl
14window.location.href = authUrl;

Step 2: Handle Callback

After user logs in, Auth0 redirects back with an authorization code:

1// Extract code from URL
2const urlParams = new URLSearchParams(window.location.search);
3const code = urlParams.get('code');
4
5// Exchange code for tokens
6const tokenResponse = await fetch(`https://${auth0Domain}/oauth/token`, {
7 method: 'POST',
8 headers: { 'Content-Type': 'application/json' },
9 body: JSON.stringify({
10 grant_type: 'authorization_code',
11 client_id: clientId,
12 client_secret: clientSecret, // Only for server-side
13 code: code,
14 redirect_uri: redirectUri
15 })
16});
17
18const { access_token } = await tokenResponse.json();

Step 3: Use Access Token

1import { HoleFoundationClient } from '@hole-foundation/api';
2
3const client = new HoleFoundationClient({
4 environment: 'https://api.theholefoundation.org',
5 token: access_token
6});
7
8const requests = await client.foiaDashboard.listRequests();

Machine-to-Machine (M2M) Authentication

Best for backend services, cron jobs, and server-to-server API calls.

Step 1: Get M2M Token

$curl -X POST "https://dev-4fszoklachwdh46m.us.auth0.com/oauth/token" \
> -H "Content-Type: application/json" \
> -d '{
> "grant_type": "client_credentials",
> "client_id": "YOUR_M2M_CLIENT_ID",
> "client_secret": "YOUR_M2M_CLIENT_SECRET",
> "audience": "https://api.theholefoundation.org"
> }'

Response:

1{
2 "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
3 "token_type": "Bearer",
4 "expires_in": 86400
5}

Step 2: Use M2M Token

1import { HoleFoundationClient } from '@hole-foundation/api';
2
3const client = new HoleFoundationClient({
4 environment: 'https://api.theholefoundation.org',
5 token: access_token
6});

Step 3: Handle Token Refresh

M2M tokens expire after 24 hours. Implement token caching and refresh:

1class TokenManager {
2 private token: string | null = null;
3 private expiresAt: number = 0;
4
5 async getToken(): Promise<string> {
6 // Return cached token if still valid
7 if (this.token && Date.now() < this.expiresAt) {
8 return this.token;
9 }
10
11 // Fetch new token
12 const response = await fetch('https://dev-4fszoklachwdh46m.us.auth0.com/oauth/token', {
13 method: 'POST',
14 headers: { 'Content-Type': 'application/json' },
15 body: JSON.stringify({
16 grant_type: 'client_credentials',
17 client_id: process.env.AUTH0_M2M_CLIENT_ID,
18 client_secret: process.env.AUTH0_M2M_CLIENT_SECRET,
19 audience: 'https://api.theholefoundation.org'
20 })
21 });
22
23 const { access_token, expires_in } = await response.json();
24
25 this.token = access_token;
26 this.expiresAt = Date.now() + (expires_in * 1000) - 60000; // Refresh 1 min early
27
28 return this.token;
29 }
30}
31
32// Usage
33const tokenManager = new TokenManager();
34
35const client = new HoleFoundationClient({
36 environment: 'https://api.theholefoundation.org',
37 token: async () => await tokenManager.getToken()
38});

API Scopes

Different endpoints require different scopes:

ScopeEndpointsDescription
read:transparency/api/v2/transparency/*Read transparency data (public)
read:foiaGET /api/foia/*Read your FOIA requests
write:foiaPOST /api/foia/*Create/update FOIA requests
read:vector/api/v1/searchSearch legal documents
chat:legal/api/chatUse AI chat assistant

Token Validation

The API validates JWT tokens on every request:

  1. Signature verification - RS256 algorithm with Auth0 public keys
  2. Issuer check - Must be https://dev-4fszoklachwdh46m.us.auth0.com/
  3. Audience check - Must be https://api.theholefoundation.org
  4. Expiration check - Token must not be expired
  5. Scope validation - Token must have required scopes

Error Responses

401 Unauthorized

Token is missing or invalid:

1{
2 "error": "Unauthorized",
3 "message": "Missing or invalid authorization token",
4 "statusCode": 401
5}

403 Forbidden

Token is valid but lacks required scopes:

1{
2 "error": "Forbidden",
3 "message": "Insufficient permissions. Required scope: write:foia",
4 "statusCode": 403
5}

Security Best Practices

✅ DO

  • Store credentials in environment variables
  • Use HTTPS for all API calls
  • Implement token refresh logic
  • Set appropriate token expiration times
  • Rotate secrets regularly
  • Use different credentials for dev/prod

❌ DON’T

  • Hardcode credentials in source code
  • Commit secrets to version control
  • Share tokens between environments
  • Use user tokens for M2M operations
  • Store tokens in localStorage (web apps)
  • Log tokens or credentials

Example: Complete M2M Flow

1// token-manager.ts
2import fetch from 'node-fetch';
3
4export class Auth0TokenManager {
5 private token: string | null = null;
6 private expiresAt: number = 0;
7
8 constructor(
9 private domain: string,
10 private clientId: string,
11 private clientSecret: string,
12 private audience: string
13 ) {}
14
15 async getAccessToken(): Promise<string> {
16 if (this.token && Date.now() < this.expiresAt) {
17 return this.token;
18 }
19
20 const response = await fetch(`https://${this.domain}/oauth/token`, {
21 method: 'POST',
22 headers: { 'Content-Type': 'application/json' },
23 body: JSON.stringify({
24 grant_type: 'client_credentials',
25 client_id: this.clientId,
26 client_secret: this.clientSecret,
27 audience: this.audience
28 })
29 });
30
31 if (!response.ok) {
32 throw new Error(`Auth0 error: ${response.statusText}`);
33 }
34
35 const { access_token, expires_in } = await response.json();
36
37 this.token = access_token;
38 this.expiresAt = Date.now() + (expires_in * 1000) - 60000;
39
40 return this.token;
41 }
42}
43
44// app.ts
45import { HoleFoundationClient } from '@hole-foundation/api';
46import { Auth0TokenManager } from './token-manager';
47
48const tokenManager = new Auth0TokenManager(
49 process.env.AUTH0_DOMAIN!,
50 process.env.AUTH0_M2M_CLIENT_ID!,
51 process.env.AUTH0_M2M_CLIENT_SECRET!,
52 process.env.AUTH0_AUDIENCE!
53);
54
55const client = new HoleFoundationClient({
56 environment: 'https://api.theholefoundation.org',
57 token: async () => await tokenManager.getAccessToken()
58});
59
60// Use the client
61const requests = await client.foiaDashboard.createRequest({
62 jurisdiction: 'federal',
63 subject: 'Freedom of Information Request',
64 description: 'Request for records regarding...'
65});

Testing Authentication

Use our health endpoint to verify your token:

$curl -X GET "https://api.theholefoundation.org/api/health" \
> -H "Authorization: Bearer YOUR_TOKEN"

Success response:

1{
2 "status": "healthy",
3 "database": "connected",
4 "version": "2.0.0",
5 "authenticated": true,
6 "userId": "auth0|1234567890"
7}

Need Help?