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.
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: Bearerheader 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
- Client sends credentials to
/api/auth/login. - Server validates credentials and creates a session (license seat check is performed for enterprise deployments).
- Server returns
access_token,refresh_token, andexpires_in(seconds until access token expires). - Client includes the access token in every request:
Authorization: Bearer <access_token>. - Before the access token expires, client calls
POST /api/auth/refreshwith the refresh token. - 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:
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 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
# }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")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 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
# }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()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
# 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 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
POSTrequest to/api/auth/loginwith your username and password as JSON. The response contains anaccess_tokenandrefresh_token. Include the access token in theAuthorization: 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_infield (in seconds) of the login and refresh responses. - How do I refresh an expired token?
- Send a
POSTrequest to/api/auth/refreshwith yourrefresh_tokenin the JSON body. The response contains a newaccess_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/providersendpoint to check which providers are enabled on your Controller. - What happens when I hit the rate limit?
- The API returns a
429 Too Many Requestsstatus 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/logoutto revoke the current session. To revoke a specific session, useDELETE /api/auth/sessions/:id. To revoke all sessions except the current one, callDELETE /api/auth/sessions/other. - Can I change my password via the API?
- Yes. Send a
PUTrequest to/api/auth/passwordwith 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.
Related Features
- 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.