Authentication#

TerraNexus supports multiple authentication methods for different use cases. This section covers how to authenticate with the TerraNexus API using JWT tokens, API keys, and web session authentication.

API Authentication Guide#

TerraNexus API Authentication Guide#

Overview#

TerraNexus provides two authentication methods for programmatic API access:

Method

Best For

Expiry

MFA Required

JWT Tokens

Interactive apps, short-term access

15 min / 30 days

Yes (if enabled)

API Keys

Servers, CI/CD, M2M automation

Never (until revoked)

No


Method 1: JWT Token Authentication#

JWT (JSON Web Tokens) provide short-lived, secure authentication. Use this method for interactive applications or when you need fine-grained session control.

Token Expiry#

Token Type

Expiry

Purpose

Access Token

15 minutes

API request authentication

Refresh Token

30 days

Obtain new access tokens

1.1 Login - Obtain Tokens#

Endpoint: POST /api/auth/login/

Without MFA:

curl -X POST \
     -H 'Content-Type: application/json' \
     -H 'Accept: application/json' \
     -d '{"email": "user@example.com", "password": "your_password"}' \
     'https://terranexus.pangaeainnovations.com/api/auth/login/'

With MFA enabled:

curl -X POST \
     -H 'Content-Type: application/json' \
     -d '{"email": "user@example.com", "password": "your_password", "mfa_code": "123456"}' \
     'https://terranexus.pangaeainnovations.com/api/auth/login/'

The mfa_code can be either:

  • A 6-digit TOTP code from your authenticator app

  • A backup code in format XXXX-XXXX (single-use)

Python example:

>>> import requests
>>> api_path = 'https://terranexus.pangaeainnovations.com/api/auth/login/'
>>> api_headers = {'Content-Type': 'application/json', 'Accept': 'application/json'}
>>> api_data = {"email": "user@example.com", "password": "your_password"}
>>> response = requests.post(api_path, headers=api_headers, json=api_data)
>>> response.json()

Success response:

{
    "success": true,
    "tokens": {
        "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
        "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
        "token_type": "Bearer",
        "expires_in": 900
    },
    "user": {
        "id": "usr_abc123def456",
        "email": "user@example.com",
        "first_name": "John",
        "last_name": "Doe",
        "requires_mfa": false
    }
}

MFA required response (HTTP 202):

{
    "success": false,
    "error": "MFA verification required",
    "mfa_required": true,
    "mfa_methods": ["totp"]
}
1.2 Using JWT Tokens#

Option 1: Authorization Header (recommended)

curl -H "Authorization: Bearer <access_token>" \
     https://terranexus.pangaeainnovations.com/ogcapi/collections

Option 2: Query Parameter

https://terranexus.pangaeainnovations.com/ogcapi/collections?token=<access_token>

Use query parameters for browser links or embedding.

1.3 Refresh Token#

Endpoint: POST /api/auth/refresh/

When your access token expires (after 15 minutes), use the refresh token to obtain a new one:

curl -X POST \
     -H 'Content-Type: application/json' \
     -d '{"refresh_token": "<refresh_token>"}' \
     'https://terranexus.pangaeainnovations.com/api/auth/refresh/'

Response:

{
    "success": true,
    "tokens": {
        "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.NEW...",
        "token_type": "Bearer",
        "expires_in": 900
    }
}
1.4 Get User Info#

Endpoint: GET /api/user/info/

curl -H "Authorization: Bearer <access_token>" \
     https://terranexus.pangaeainnovations.com/api/user/info/
1.5 Logout#

Endpoint: POST /api/auth/logout/

curl -X POST \
     -H "Authorization: Bearer <access_token>" \
     -H "Content-Type: application/json" \
     -d '{"refresh_token": "<refresh_token>"}' \
     https://terranexus.pangaeainnovations.com/api/auth/logout/

Method 2: API Key Authentication#

API Keys provide long-lived, simple authentication for machine-to-machine (M2M) access. Ideal for servers, CI/CD pipelines, and automated scripts.

Key Features#
  • No expiry – keys remain valid until manually revoked

  • No MFA required – MFA is verified when the key is created via web UI

  • Revocable – disable keys instantly if compromised

  • Auditable – usage tracking (last used, use count)

2.1 Creating API Keys#

API Keys are created from the Profile page in the web UI:

  1. Login to TerraNexus (complete MFA if enabled)

  2. Go to ProfileAPI AccessMethod 2: API Keys tab

  3. Enter a name (e.g., “Production Server”)

  4. Click Generate API Key

  5. Copy the key immediately – it will not be shown again!

Or via API (requires JWT authentication):

curl -X POST \
     -H "Authorization: Bearer <access_token>" \
     -H "Content-Type: application/json" \
     -d '{"name": "CI/CD Pipeline", "scopes": ["read", "write"]}' \
     https://terranexus.pangaeainnovations.com/api/keys/create/

Response:

{
    "success": true,
    "key_id": "key_abc123def456",
    "api_key": "tnx_a1b2c3d4e5f6g7h8i9j0...",
    "name": "CI/CD Pipeline",
    "scopes": ["read", "write"],
    "created_at": "2025-02-01T10:00:00Z",
    "message": "Save this API key now - it won't be shown again!"
}
2.2 Using API Keys#

Option 1: X-API-Key Header (recommended)

curl -H "X-API-Key: tnx_your_api_key_here" \
     https://terranexus.pangaeainnovations.com/ogcapi/collections

Option 2: Query Parameter

https://terranexus.pangaeainnovations.com/ogcapi/collections?api_key=tnx_your_api_key_here

Python example:

>>> import requests
>>> api_key = 'tnx_your_api_key_here'
>>>
>>> # Using header
>>> response = requests.get(
...     'https://terranexus.pangaeainnovations.com/ogcapi/collections',
...     headers={'X-API-Key': api_key}
... )
>>>
>>> # Using query parameter
>>> response = requests.get(
...     'https://terranexus.pangaeainnovations.com/ogcapi/collections',
...     params={'api_key': api_key}
... )
2.3 Listing API Keys#

Endpoint: GET /api/keys/

curl -H "Authorization: Bearer <access_token>" \
     https://terranexus.pangaeainnovations.com/api/keys/

Response:

{
    "success": true,
    "keys": [
        {
            "key_id": "key_abc123",
            "key_prefix": "tnx_a1b2c3d4",
            "name": "Production Server",
            "scopes": ["read", "write"],
            "created_at": "2025-02-01T10:00:00Z",
            "last_used_at": "2025-02-01T14:30:00Z",
            "use_count": 42,
            "is_active": true
        }
    ]
}
2.4 Revoking API Keys#

Endpoint: POST /api/keys/<key_id>/revoke/

curl -X POST \
     -H "Authorization: Bearer <access_token>" \
     https://terranexus.pangaeainnovations.com/api/keys/key_abc123/revoke/

Response:

{
    "success": true,
    "message": "API key revoked successfully"
}

Complete Python Client Example#

import requests

class TerraNexusAPI:
    """
    TerraNexus API client supporting both JWT and API Key authentication.

    Examples
    --------

    Using API Key (recommended for M2M)::

        >>> api = TerraNexusAPI(api_key='tnx_your_api_key_here')
        >>> collections = api.get_collections()

    Using JWT (for interactive apps)::

        >>> api = TerraNexusAPI(email='user@example.com', password='***')
        >>> collections = api.get_collections()

    Using JWT with MFA::

        >>> api = TerraNexusAPI(email='user@example.com', password='***', mfa_code='123456')
        >>> collections = api.get_collections()
    """

    def __init__(self, api_key=None, email=None, password=None, mfa_code=None):
        self.base_url = 'https://terranexus.pangaeainnovations.com'
        self.api_key = api_key
        self.access_token = None
        self.refresh_token = None

        # If credentials provided, login with JWT
        if email and password:
            self._login(email, password, mfa_code)

    def _login(self, email, password, mfa_code=None):
        """Authenticate with JWT tokens."""
        payload = {'email': email, 'password': password}
        if mfa_code:
            payload['mfa_code'] = mfa_code

        response = requests.post(
            f'{self.base_url}/api/auth/login/',
            headers={'Content-Type': 'application/json'},
            json=payload
        )
        data = response.json()

        if data.get('mfa_required'):
            raise Exception('MFA required - provide mfa_code parameter')

        if data.get('success'):
            self.access_token = data['tokens']['access_token']
            self.refresh_token = data['tokens']['refresh_token']
        else:
            raise Exception(f"Login failed: {data.get('error')}")

    def _refresh_tokens(self):
        """Refresh expired JWT access token."""
        if not self.refresh_token:
            return False

        response = requests.post(
            f'{self.base_url}/api/auth/refresh/',
            json={'refresh_token': self.refresh_token}
        )
        data = response.json()

        if data.get('success'):
            self.access_token = data['tokens']['access_token']
            return True
        return False

    def _get_headers(self):
        """Get authentication headers."""
        if self.api_key:
            return {'X-API-Key': self.api_key}
        elif self.access_token:
            return {'Authorization': f'Bearer {self.access_token}'}
        return {}

    def _request(self, method, endpoint, **kwargs):
        """Make authenticated request with auto-refresh for JWT."""
        url = f'{self.base_url}{endpoint}'
        headers = kwargs.pop('headers', {})
        headers.update(self._get_headers())

        response = requests.request(method, url, headers=headers, **kwargs)

        # Auto-refresh JWT on 401 (not applicable for API keys)
        if response.status_code == 401 and self.access_token:
            if self._refresh_tokens():
                headers.update(self._get_headers())
                response = requests.request(method, url, headers=headers, **kwargs)

        return response

    def get_collections(self):
        """Get all collections."""
        response = self._request('GET', '/ogcapi/collections')
        return response.json()

    def get_collection_items(self, collection_id, limit=10):
        """Get items from a collection."""
        response = self._request(
            'GET',
            f'/ogcapi/collections/{collection_id}/items',
            params={'limit': limit}
        )
        return response.json()

    def get_dggs_zones(self, collection_id, dggs_id='H3'):
        """Get DGGS zones for a collection."""
        response = self._request(
            'GET',
            f'/ogcapi/collections/{collection_id}/dggs/{dggs_id}/zones'
        )
        return response.json()


# Usage examples
if __name__ == '__main__':
    # Option 1: API Key (recommended for automation)
    api = TerraNexusAPI(api_key='tnx_your_api_key_here')

    # Option 2: JWT without MFA
    # api = TerraNexusAPI(email='user@example.com', password='your_password')

    # Option 3: JWT with MFA
    # api = TerraNexusAPI(email='user@example.com', password='your_password', mfa_code='123456')

    # Get collections
    collections = api.get_collections()
    print(f"Found {len(collections.get('collections', []))} collections")

OGC API Endpoints#

All these endpoints support both JWT and API Key authentication:

Endpoint

Method

Description

/ogcapi/

GET

API landing page

/ogcapi/collections

GET

List collections

/ogcapi/collections/{id}

GET

Collection metadata

/ogcapi/collections/{id}/items

GET

Collection items (features)

/ogcapi/collections/{id}/items/{fid}

GET

Single feature

/ogcapi/collections/{id}/dggs/{dggsId}/zones

GET

DGGS zones

/ogcapi/processes

GET

List processes

/ogcapi/processes/{id}/execution

POST

Execute process


MFA (Multi-Factor Authentication)#

MFA with JWT Tokens#

If MFA is enabled on your account, the /api/auth/login/ endpoint requires an mfa_code:

{
    "email": "user@example.com",
    "password": "your_password",
    "mfa_code": "123456"
}

The mfa_code can be:

  • TOTP code: 6-digit code from your authenticator app (e.g., 123456)

  • Backup code: One-time code in format XXXX-XXXX (e.g., ABCD-1234)

Important: Backup codes are single-use. Once used, they are automatically invalidated and cannot be used again.

MFA with API Keys#

API Keys do not require MFA during API calls because:

  1. MFA was verified when you created the key via the web UI

  2. Keys are designed for automated/M2M access where no human is present

This makes API Keys the preferred method for servers, CI/CD pipelines, and automated scripts.


Error Responses#

Status

Error

Description

400

Invalid JSON payload

Request body is not valid JSON

400

Email and password are required

Missing credentials

401

Invalid email or password

Authentication failed

401

Invalid MFA code

MFA verification failed

401

Invalid or expired token

JWT token is invalid/expired

401

Invalid API key

API key is invalid or revoked

401

Authorization header required

Missing authentication

429

Account temporarily locked

Too many failed attempts (5 min lockout)


Security Best Practices#

  1. Use API Keys for M2M – Avoid storing passwords in scripts

  2. Use JWT for interactive apps – Short expiry limits exposure

  3. Enable MFA – Adds security layer for JWT login

  4. Store credentials securely – Never commit keys/tokens to git

  5. Use HTTPS – All API calls must use HTTPS

  6. Revoke compromised keys – Disable immediately from Profile page

  7. Use descriptive key names – Makes auditing easier

  8. Monitor usage – Check last_used_at and use_count for anomalies

Web Authentication Guide#

TerraNexus Web Authentication Guide#

Overview#

This guide covers the browser-based (interactive) authentication flows for TerraNexus. These are used when accessing the platform through a web browser.

Authentication Method

URL

Description

Email/Password

/user/login/

Standard login with optional MFA

Google OAuth2

/user/oauth/google/

Sign in with Google

GitHub OAuth2

/user/oauth/github/

Sign in with GitHub


Authentication Flow Diagram#

┌─────────────┐     ┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│   Login     │────▶│  Password   │────▶│    MFA      │────▶│  Dashboard  │
│    Page     │     │   Check     │     │  (if enabled)│     │             │
└─────────────┘     └─────────────┘     └─────────────┘     └─────────────┘
       │                                       │
       │            ┌─────────────┐            │
       └───────────▶│   OAuth2    │────────────┘
                    │  Provider   │
                    └─────────────┘

1. Standard Login (Email/Password)#

URL: /user/login/#
Flow#
  1. User navigates to login page

  2. User enters email and password

  3. System validates credentials

  4. If MFA enabled → redirect to MFA verification

  5. If MFA not enabled → create session and redirect to dashboard

Form Fields#

Field

Type

Required

Description

email

email

Yes

User’s email address

password

password

Yes

User’s password

remember_me

checkbox

No

Extend session duration

next

hidden

No

URL to redirect after login

Success Response#
  • Session cookie set

  • Redirect to next URL or dashboard (/)

Error Responses#

Error

Message

Action

Invalid credentials

“Invalid email or password”

Re-display form

Account locked

“Account temporarily locked. Try again in 5 minutes.”

Wait and retry

Account inactive

“Account is not active”

Contact support

MFA required

N/A

Redirect to /user/mfa/verify/


2. MFA Verification#

URL: /user/mfa/verify/#
When Required#

MFA verification is required when:

  • User has MFA enabled on their account

  • User successfully entered email/password

  • User has not yet verified MFA in current login flow

Flow#
  1. User is redirected from login page

  2. User enters 6-digit TOTP code OR backup code

  3. System verifies code

  4. If valid → create session and redirect

  5. If invalid → show error and allow retry

Form Fields#

Field

Type

Required

Description

mfa_code

text

Yes

6-digit TOTP code or backup code

Accepted Code Formats#

Code Type

Format

Example

Notes

TOTP

6 digits

123456

From authenticator app

Backup

XXXX-XXXX

ABCD-1234

Single-use

Important: Backup Codes are Single-Use#

When you use a backup code:

  1. The code is verified against your stored backup codes

  2. If valid, the code is permanently deleted from your account

  3. That code can never be used again

  4. You have a limited number of backup codes (10 generated during MFA setup)

Error Responses#

Error

Message

Action

Invalid code

“Invalid MFA code”

Re-display form

Expired session

“Session expired”

Redirect to login

No backup codes left

“No backup codes remaining”

Disable and re-enable MFA


3. MFA Setup#

URL: /user/mfa/setup/#
Prerequisites#
  • User must be logged in

  • User must not have MFA already enabled

Flow#
  1. User navigates to MFA setup (from Profile page)

  2. System generates TOTP secret and QR code

  3. User scans QR code with authenticator app

  4. User enters verification code to confirm setup

  5. System displays backup codes (one time only!)

  6. MFA is now enabled

Supported Authenticator Apps#
  • Google Authenticator

  • Microsoft Authenticator

  • Authy

  • 1Password

  • Any TOTP-compatible app

Backup Codes#

During MFA setup, 10 backup codes are generated:

ABCD-1234
EFGH-5678
IJKL-9012
... (10 total)

Critical:

  • Save these codes in a secure location

  • Each code can only be used once

  • They are your only recovery method if you lose your authenticator


4. MFA Disable#

URL: /user/mfa/disable/#
Prerequisites#
  • User must be logged in

  • User must have MFA enabled

  • User must verify current MFA code to disable

Flow#
  1. User navigates to MFA settings

  2. User clicks “Disable MFA”

  3. User enters current TOTP code to confirm

  4. MFA is disabled

  5. All backup codes are deleted


5. OAuth2 Login (Google/GitHub)#

URLs#

Provider

Initiate URL

Callback URL

Google

/user/oauth/google/

/user/oauth/google/callback/

GitHub

/user/oauth/github/

/user/oauth/github/callback/

Flow#
  1. User clicks “Sign in with Google/GitHub” on login page

  2. User is redirected to OAuth provider

  3. User authenticates with provider

  4. Provider redirects back to callback URL

  5. System creates/links account and creates session

  6. User is redirected to dashboard

Account Linking#

Scenario

Behavior

New email

Create new account

Existing email (same provider)

Log in to existing account

Existing email (different provider)

Link to existing account

Existing email (password account)

Link OAuth to existing account

OAuth2 Permissions Requested#

Google:

  • openid - Basic identity

  • email - Email address

  • profile - Name and profile picture

GitHub:

  • read:user - Basic user info

  • user:email - Email address


6. Signup (Account Registration)#

URL: /user/signup/#
Flow#
  1. User navigates to signup page

  2. User fills out registration form

  3. System validates input

  4. System sends verification email

  5. User clicks verification link

  6. Account is activated

Form Fields#

Field

Type

Required

Validation

email

email

Yes

Valid email, not already registered

password

password

Yes

NIST-compliant (see below)

password_confirm

password

Yes

Must match password

first_name

text

Yes

1-50 characters

last_name

text

Yes

1-50 characters

Password Requirements (NIST SP 800-63B)#
  • Minimum 8 characters

  • No maximum length restriction

  • Checked against common password list

  • No composition rules (uppercase, numbers, etc.)

  • Context-aware (rejects passwords containing email/name)

Email Verification#

After signup:

  1. Verification email sent to provided address

  2. Email contains unique activation link

  3. Link expires after 24 hours

  4. Clicking link activates account

  5. User can then log in


7. Password Reset#

Request URL: /user/password-reset/#
Confirm URL: /user/password-reset/confirm/<token>/#
Flow#
  1. User clicks “Forgot Password” on login page

  2. User enters email address

  3. System sends reset email (if account exists)

  4. User clicks link in email

  5. User enters new password

  6. Password is updated

  7. User can log in with new password

Security Features#
  • Reset tokens expire after 1 hour

  • Tokens are single-use

  • Rate limited (max 3 requests per 15 minutes)

  • Same message shown whether email exists or not (prevents enumeration)


8. Password Change (Authenticated)#

URL: /user/password-change/#
Prerequisites#
  • User must be logged in

Flow#
  1. User navigates to Profile → Security

  2. User enters current password

  3. User enters new password (twice)

  4. System validates and updates password

  5. Existing sessions remain valid

Form Fields#

Field

Type

Required

Description

current_password

password

Yes

Current password

new_password

password

Yes

New password (NIST-compliant)

confirm_password

password

Yes

Must match new password


9. Logout#

URL: /user/logout/#
Flow#
  1. User clicks “Logout”

  2. Session is invalidated in MongoDB

  3. Session cookie is cleared

  4. User is redirected to login page

Session Cleanup#
  • Server-side session data is deleted

  • Session cookie is expired

  • Any cached authentication data is cleared


10. Profile Management#

URL: /user/profile/#
Features#

Section

Description

Personal Info

Update name, email

Security

Change password, MFA settings

API Access

Generate JWT tokens, manage API keys

Sessions

View/revoke active sessions


Session Management#

Session Duration#

Setting

Default

With “Remember Me”

Session timeout

2 hours

30 days

Idle timeout

30 minutes

24 hours

Session Storage#

Sessions are stored in MongoDB with:

  • Unique session key

  • User ID

  • Creation timestamp

  • Last activity timestamp

  • IP address

  • User agent

Concurrent Sessions#
  • Multiple sessions allowed (different devices)

  • Sessions can be viewed/revoked from Profile

  • All sessions invalidated on password change (optional)


Security Features#

Rate Limiting#

Action

Limit

Lockout

Login attempts

5 per 5 minutes

5 minute lockout

MFA attempts

5 per 5 minutes

5 minute lockout

Password reset

3 per 15 minutes

15 minute cooldown

CSRF Protection#

All forms include CSRF tokens:

<form method="post">
    {% csrf_token %}
    <!-- form fields -->
</form>
Secure Cookies#

Cookie

Flags

Session

HttpOnly, Secure, SameSite=Lax

CSRF

HttpOnly, Secure, SameSite=Lax


URL Reference#

URL

Method

View

Description

/user/login/

GET, POST

LoginView

Login form and processing

/user/logout/

GET, POST

LogoutView

Logout and session cleanup

/user/signup/

GET, POST

SignupView

Registration form

/user/mfa/verify/

GET, POST

MFAVerifyView

MFA code verification

/user/mfa/setup/

GET, POST

MFASetupView

Enable MFA

/user/mfa/disable/

GET, POST

MFADisableView

Disable MFA

/user/oauth/<provider>/

GET

OAuth2LoginView

Initiate OAuth2

/user/oauth/<provider>/callback/

GET

OAuth2CallbackView

OAuth2 callback

/user/password-reset/

GET, POST

PasswordResetRequestView

Request reset

/user/password-reset/confirm/<token>/

GET, POST

PasswordResetConfirmView

Reset password

/user/password-change/

GET, POST

PasswordChangeView

Change password

/user/profile/

GET, POST

ProfileView

Profile management


Error Pages#

Status

Template

Description

400

400.html

Bad request

401

401.html

Unauthorized

403

403.html

Forbidden

404

404.html

Not found

500

500.html

Server error


Troubleshooting#

“Invalid email or password”#
  • Check email spelling

  • Ensure Caps Lock is off

  • Try password reset if forgotten

“Account temporarily locked”#
  • Wait 5 minutes

  • Contact support if persists

“MFA code invalid”#
  • Ensure authenticator app time is synced

  • Try a backup code

  • Wait for next code cycle (30 seconds)

“Session expired”#
  • Re-login

  • Check browser cookie settings

  • Clear browser cache

OAuth2 Errors#

Error

Cause

Solution

“State mismatch”

CSRF protection triggered

Clear cookies, try again

“Access denied”

User cancelled OAuth

Try again or use password

“Provider not configured”

Missing OAuth credentials

Contact administrator