NetStacksNetStacks

API Authentication

Learn how to authenticate with the NetStacks API using JWT tokens, service tokens, and how to handle rate limiting.

Overview

The NetStacks API uses JWT (JSON Web Tokens) for authentication. Every API request (except login and health checks) must include a valid access token in the Authorization header. NetStacks supports three authentication methods:

  • Local authentication — Username and password verified against the built-in user database. Returns an access token (short-lived) and a refresh token (long-lived).
  • LDAP / Active Directory — Authenticate against your existing directory service. The Controller validates credentials via LDAP bind and issues JWT tokens on success.
  • OIDC / SSO — Single sign-on via OpenID Connect providers (Okta, Azure AD, Google Workspace). Browser-based redirect flow returns tokens after provider authorization.

All methods return the same JWT token pair. Once authenticated, API usage is identical regardless of the authentication provider.

Rate Limiting

Login endpoints are rate-limited per IP address to prevent brute force attacks. If you exceed the limit, the API returns a 429 Too Many Requests response. See the Error Codes page for details.

How It Works

JWT Token Flow

When you authenticate via POST /api/auth/login, the API returns two tokens:

  • Access token — A short-lived JWT (typically 15–60 minutes) included in the Authorization: Bearer header of every API request. Contains the user ID, organization ID, roles, and permissions as claims.
  • Refresh token — A long-lived token (7 days) used to obtain a new access token without re-entering credentials. Stored securely in the database and revocable at any time.

Token Lifecycle

  1. Client sends credentials to /api/auth/login.
  2. Server validates credentials and creates a session (license seat check is performed for enterprise deployments).
  3. Server returns access_token, refresh_token, and expires_in (seconds until access token expires).
  4. Client includes the access token in every request: Authorization: Bearer <access_token>.
  5. Before the access token expires, client calls POST /api/auth/refresh with the refresh token.
  6. Server issues a new access token and returns it.

LDAP Authentication

For LDAP/AD environments, use the POST /api/auth/ldap endpoint instead. The Controller performs an LDAP bind with the provided credentials and, on success, creates or updates a local user record and returns JWT tokens.

OIDC / SSO Flow

OIDC authentication uses a browser redirect flow. Call GET /api/auth/oidc/authorize to get the redirect URL to your identity provider. After the user authenticates, the provider redirects to /api/auth/oidc/callback which creates the session and issues tokens.

Session Management

Each login creates a session record. Users can view active sessions via GET /api/auth/sessions and revoke individual sessions via DELETE /api/auth/sessions/:id. The DELETE /api/auth/sessions/other endpoint revokes all sessions except the current one.

Step-by-Step Guide

1. Check Available Auth Providers

Before authenticating, you can discover which providers are enabled on your Controller:

providers-response.jsonjson
GET /api/auth/providers

Response:
{
  "providers": [
    { "name": "local", "enabled": true },
    { "name": "ldap", "enabled": true },
    { "name": "oidc", "enabled": false }
  ]
}

2. Authenticate to Get Tokens

Send your credentials to the login endpoint. Use /api/auth/login for local accounts or /api/auth/ldap for LDAP/AD accounts.

3. Include the Access Token in Requests

Add the Authorization header to every API call:

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

4. Refresh Before Expiry

Track the expires_in value from the login response. Before the access token expires, call the refresh endpoint to get a new one without re-authenticating.

5. Handle Rate Limiting

If you receive a 429 response, wait before retrying. Implement exponential backoff in automated scripts: wait 1 second, then 2, then 4, up to a maximum of 30 seconds.

6. Log Out When Done

Call POST /api/auth/logout to revoke the current session and invalidate all associated tokens.

Code Examples

Login (Local Authentication)

login.shbash
# Login with username and password
curl -X POST https://netstacks.example.net/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{
    "username": "netops-api",
    "password": "s3cur3-passw0rd!"
  }'

# Response:
# {
#   "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
#   "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
#   "expires_in": 3600
# }
login.pypython
import requests

base_url = "https://netstacks.example.net/api"

# Login
resp = requests.post(f"{base_url}/auth/login", json={
    "username": "netops-api",
    "password": "s3cur3-passw0rd!"
})
tokens = resp.json()
print(f"Access token expires in {tokens['expires_in']} seconds")

# Create reusable headers
headers = {"Authorization": f"Bearer {tokens['access_token']}"}

# Make an authenticated request
devices = requests.get(f"{base_url}/devices", headers=headers)
print(f"Found {devices.json()['total']} devices")
login.tstypescript
const BASE_URL = "https://netstacks.example.net/api";

interface TokenResponse {
  access_token: string;
  refresh_token: string;
  expires_in: number;
}

async function login(username: string, password: string): Promise<TokenResponse> {
  const resp = await fetch(`${BASE_URL}/auth/login`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ username, password }),
  });

  if (!resp.ok) {
    throw new Error(`Login failed: ${resp.status} ${resp.statusText}`);
  }

  return resp.json();
}

// Usage
const tokens = await login("netops-api", "s3cur3-passw0rd!");
console.log(`Token expires in ${tokens.expires_in} seconds`);

// Authenticated request
const devices = await fetch(`${BASE_URL}/devices`, {
  headers: { Authorization: `Bearer ${tokens.access_token}` },
});
const data = await devices.json();
console.log(`Found ${data.total} devices`);

Token Refresh

refresh.shbash
# Refresh an expiring access token
curl -X POST https://netstacks.example.net/api/auth/refresh \
  -H "Content-Type: application/json" \
  -d '{
    "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
  }'

# Response:
# {
#   "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...(new)",
#   "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
#   "expires_in": 3600
# }
client.pypython
def refresh_token(base_url: str, refresh_token: str) -> dict:
    """Refresh an expired access token."""
    resp = requests.post(f"{base_url}/auth/refresh", json={
        "refresh_token": refresh_token
    })
    resp.raise_for_status()
    return resp.json()

# Auto-refresh wrapper
class NetStacksClient:
    def __init__(self, base_url: str, username: str, password: str):
        self.base_url = base_url
        self.tokens = requests.post(
            f"{base_url}/auth/login",
            json={"username": username, "password": password}
        ).json()

    @property
    def headers(self):
        return {"Authorization": f"Bearer {self.tokens['access_token']}"}

    def refresh(self):
        self.tokens = refresh_token(self.base_url, self.tokens["refresh_token"])

    def get(self, path: str) -> dict:
        resp = requests.get(f"{self.base_url}{path}", headers=self.headers)
        if resp.status_code == 401:
            self.refresh()
            resp = requests.get(f"{self.base_url}{path}", headers=self.headers)
        resp.raise_for_status()
        return resp.json()
client.tstypescript
async function refreshAccessToken(
  baseUrl: string,
  refreshToken: string
): Promise<TokenResponse> {
  const resp = await fetch(`${baseUrl}/auth/refresh`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ refresh_token: refreshToken }),
  });

  if (!resp.ok) {
    throw new Error("Token refresh failed — re-authenticate");
  }

  return resp.json();
}

// Auto-refresh wrapper
class NetStacksClient {
  private tokens: TokenResponse;
  private refreshTimer?: ReturnType<typeof setTimeout>;

  constructor(private baseUrl: string, tokens: TokenResponse) {
    this.tokens = tokens;
    this.scheduleRefresh();
  }

  private scheduleRefresh() {
    // Refresh 60 seconds before expiry
    const ms = (this.tokens.expires_in - 60) * 1000;
    this.refreshTimer = setTimeout(() => this.refresh(), ms);
  }

  private async refresh() {
    this.tokens = await refreshAccessToken(this.baseUrl, this.tokens.refresh_token);
    this.scheduleRefresh();
  }

  async get<T>(path: string): Promise<T> {
    const resp = await fetch(`${this.baseUrl}${path}`, {
      headers: { Authorization: `Bearer ${this.tokens.access_token}` },
    });
    if (resp.status === 401) {
      await this.refresh();
      return this.get(path);
    }
    return resp.json();
  }
}

Get Current User Info

me.shbash
# Get info about the authenticated user
curl https://netstacks.example.net/api/auth/me \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."

# Response:
# {
#   "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
#   "org_id": "org-uuid-here",
#   "username": "netops-api",
#   "email": "netops@example.net",
#   "display_name": "Network Ops API Account",
#   "roles": ["network-admin"],
#   "permissions": ["ManageDevices", "ManageCredentials", "ViewAuditLogs"]
# }

Logout

logout.shbash
# Logout and revoke the current session
curl -X POST https://netstacks.example.net/api/auth/logout \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."

# Response:
# {
#   "message": "Logged out successfully",
#   "sessions_revoked": 1
# }

Questions & Answers

How do I authenticate to the NetStacks API?
Send a POST request to /api/auth/login with your username and password as JSON. The response contains an access_token and refresh_token. Include the access token in the Authorization: Bearer <token> header of all subsequent requests.
How long do access tokens last?
Access token lifetime is configured by the server administrator, typically between 15 and 60 minutes. The exact value is returned in the expires_in field (in seconds) of the login and refresh responses.
How do I refresh an expired token?
Send a POST request to /api/auth/refresh with your refresh_token in the JSON body. The response contains a new access_token. Refresh tokens are valid for 7 days by default.
What authentication providers does NetStacks support?
NetStacks supports three providers: local username/password, LDAP/Active Directory, and OIDC/SSO (OpenID Connect). Use the GET /api/auth/providers endpoint to check which providers are enabled on your Controller.
What happens when I hit the rate limit?
The API returns a 429 Too Many Requests status code. Login endpoints are rate-limited per IP address to prevent brute force attacks. Wait before retrying, and implement exponential backoff in automated scripts.
How do I revoke a token or end a session?
Call POST /api/auth/logout to revoke the current session. To revoke a specific session, use DELETE /api/auth/sessions/:id. To revoke all sessions except the current one, call DELETE /api/auth/sessions/other.
Can I change my password via the API?
Yes. Send a PUT request to /api/auth/password with your current password and the new password in the request body. This only works for local accounts, not LDAP or OIDC.

Troubleshooting

401 Unauthorized

The access token is missing, expired, or invalid. Check that you are including the Authorization: Bearer <token> header and that the token has not expired. Call the refresh endpoint to get a new access token.

403 Forbidden

The token is valid, but the user lacks the required permission for the requested operation. Check the user's roles in Roles & Permissions. The GET /api/auth/me endpoint shows the current user's roles and permissions.

429 Too Many Requests

You have exceeded the login rate limit. This is an IP-based limit to prevent brute force attacks. Wait at least 60 seconds before trying again. Automated scripts should implement exponential backoff.

CORS Errors in Browser Applications

If you are calling the API from a browser-based application, ensure that your origin is included in the Controller's allowed CORS origins configuration. Check the NETSTACKS_CORS_ORIGINS environment variable or the admin settings.

LDAP Authentication Failing

Verify the LDAP configuration in Admin > Settings. Use the POST /api/auth/ldap/test endpoint (requires admin authentication) to test the LDAP connection. Common issues include incorrect bind DN, wrong base DN, or network connectivity to the LDAP server.

  • User Management — Create and manage user accounts, assign roles, and configure authentication providers.
  • Roles & Permissions — Define roles and assign fine-grained permissions that control API access.
  • Devices API — Manage network devices programmatically after authenticating.
  • Templates API — Create and render configuration templates via the API.
  • Error Codes — Complete reference for API error responses including authentication errors.