# Authentication

## Authentication

To interact with the METI API, you must receive a client id and client secret for your Custodian account from MillPont administrators.\
\
This API uses **Auth0 JWT Bearer tokens** for authentication. Each client gets dedicated credentials with account-specific access.

### 1. Get an Access Token (Auth0)

#### Request

**URL:** https\://\<AUTH0\_DOMAIN>/oauth/token\
**Method:** POST\
**Headers:** Content-Type: application/json\
**Body**\
`{ "client_id": "<YOUR_CLIENT_ID>", "client_secret": "<YOUR_CLIENT_SECRET>", "audience": "https://api.meti.millpont.com", "grant_type": "client_credentials", "scope": "read:sources write:sources delete:sources" }`

#### Example (cURL)

```
curl -X POST "https://<AUTH0_DOMAIN>/oauth/token" \
  -H "Content-Type: application/json" \
  -d '{
    "client_id": "<YOUR_CLIENT_ID>",
    "client_secret": "<YOUR_CLIENT_SECRET>",
    "audience": "https://api.meti.millpont.com",
    "grant_type": "client_credentials",
    "scope": "read:sources write:sources delete:sources"
  }'

```

#### **Response**

`{`\
`"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",`\
`"token_type": "Bearer",`\
`"expires_in": 3600,`\
`"scope": "read:sources write:sources delete:sources"`\
`}`

Key fields:

* `access_token` (string): The JWT used to authenticate with the METI API.
* `token_type` (string): Always `Bearer`.
* `expires_in` (integer): Lifetime of the token in seconds.
* `scope` (string): Space-separated API permissions granted to this token.

Depending on your Auth0 configuration, the token may also include custom METI claims such as:

* `https://api.meti.millpont.com/account_id`
* `https://api.meti.millpont.com/role`
* `https://api.meti.millpont.com/client_name`
* `https://api.meti.millpont.com/v1_client_id`

These are used by the API to associate requests with accounts and roles.

### 2. Using the Access Token with METI API

Once you have an `access_token`, include it in the `Authorization` header for all METI API requests.

**Format**

```
Authorization: Bearer <access_token>
```

#### Example: List Sources

```bash
curl -X GET "https://api.meti.millpont.com/sources" \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
```

#### Example: Filtered by Methodology

```bash
curl -X GET "https://api.meti.millpont.com/sources?methodology=Agriculture" \
  -H "Authorization: Bearer <access_token>"
```

***

### 3. Token Expiration & Automated Workflows

Access tokens are valid for the duration specified in `expires_in`.\
For long-running or automated workflows, you should:

1. **Store the token and its expiry time**.
2. **Check validity before each request**.
3. **Request a new token automatically when needed**.

#### Steps for Token Renewal

1. **Track Token Expiration**
   * When you receive `expires_in`, compute an expiry timestamp, e.g.:

     ```python
     expiry_timestamp = time.time() + response_data["expires_in"]
     ```
2. **Check Before Each API Call**
   * If `time.time() >= expiry_timestamp`, request a new token from Auth0.
3. **Automate Token Requests**
   * Implement a helper that handles token fetching and refreshing, and reuse it across your application.

***

### 4. Minimal Python Example (Client Credentials + Auth Header)

Below is a simplified example inspired by your test script that:

* Requests an access token from Auth0.
* Automatically refreshes it when expired.
* Calls the METI API `/sources` endpoint.

```python
import time
import os
import requests

AUTH0_DOMAIN = os.getenv("AUTH0_DOMAIN")
AUTH0_AUDIENCE = os.getenv("AUTH0_AUDIENCE", "https://api.meti.millpont.com")
CLIENT_ID = os.getenv("ARVA_CLIENT_ID")       # or generic CLIENT_ID
CLIENT_SECRET = os.getenv("ARVA_CLIENT_SECRET")
BASE_URL = "https://api.meti.millpont.com"

token = None
token_expiry = 0  # epoch seconds

def get_access_token():
    global token, token_expiry

    token_url = f"https://{AUTH0_DOMAIN}/oauth/token"
    payload = {
        "client_id": CLIENT_ID,
        "client_secret": CLIENT_SECRET,
        "audience": AUTH0_AUDIENCE,
        "grant_type": "client_credentials",
        "scope": "read:sources write:sources delete:sources"
    }

    response = requests.post(token_url, json=payload, timeout=10)
    response.raise_for_status()
    data = response.json()

    token = data["access_token"]
    # Subtract a small buffer so we refresh slightly before actual expiration
    token_expiry = time.time() + data["expires_in"] - 30

def get_auth_headers():
    global token, token_expiry

    if token is None or time.time() >= token_expiry:
        get_access_token()

    return {
        "Authorization": f"Bearer {token}",
        "Content-Type": "application/json"
    }

def get_sources():
    headers = get_auth_headers()
    response = requests.get(f"{BASE_URL}/sources", headers=headers, timeout=10)
    response.raise_for_status()
    return response.json()

if __name__ == "__main__":
    sources = get_sources()
    print(f"Found {len(sources)} sources")
```

**Benefits of this pattern**

* Prevents downtime due to expired tokens.
* Keeps API access continuous for long-running or large-scale jobs.
* Centralizes authentication logic in one place.

***

### 5. Scopes & Permissions

Certain endpoints require specific scopes. For example:

* `read:sources` – read access to sources
* `write:sources` – create/update sources
* `delete:sources` – delete sources

If your token does not include the necessary scopes, calls may fail with `403 Forbidden`.\
Your Auth0 administrator must grant the appropriate scopes to your application in the Auth0 dashboard.

***

### 6. Common Errors

* **401 Unauthorized**
  * Missing `Authorization` header.
  * Malformed token.
  * Expired token.
  * Using the wrong Auth0 domain or audience when requesting the token.
* **403 Forbidden**
  * Token is valid, but does not have the required scopes (permissions) for the endpoint.
  * Ask your Auth0 administrator to update the application’s API permissions (e.g., `read:sources`, `write:sources`, `delete:sources`).

***

By integrating Auth0 client-credentials authentication and automated token management as shown above, you can maintain secure, uninterrupted access to the METI API—even for complex, long-running workflows.
