Authentication & OAuthv7.0.20
The three ways to obtain a bearer token — session, personal access token, and OAuth — how to manage tokens once you hold them, and the discovery documents that let standards-based clients configure themselves.
Every call to the Netmon REST API is made on behalf of a user, and every call carries a bearer token in the Authorization header.
How authentication works
Netmon supports three credentials, all of which resolve to a bearer token you send as Authorization: Bearer <token>:
- A session token from
POST /api/login— the credential the web UI mints for itself when you sign in. It works for scripts too, but it is tied to the SPA’s per-browser lifecycle (a fresh login revokes the previous one). Most integrators should prefer a personal access token instead. - A personal access token (PAT) — a long-lived token you mint once and paste into a script or scheduler. This is the recommended credential for automation.
- An OAuth token — obtained through a browser sign-in. This is what installed and interactive clients (the PowerShell module, the Wireshark extcap satellite, MCP clients) use so a human can consent in a browser without the client ever seeing the user’s password.
Every token acts as the user who created it. On every direct REST call the appliance enforces that user’s permissions — the permission:<slug> shown on each endpoint throughout this guide. Tokens also carry scopes (mcp:devices, mcp:logs, and so on), but scopes gate the separate MCP tool surface only; they do not restrict direct REST calls. A token’s REST reach is its owner’s permissions, full stop. The eight canonical scopes are listed in the OAuth sections below and in the Permissions & Scopes reference.
The permission:api gate. Minting a PAT, or completing any OAuth consent flow, requires the calling user to hold permission:api (or sa, which bypasses every permission). This is the single authorization axis shared by the PAT and OAuth paths — permission:api is can this user obtain a programmatic token at all, independent of what the token may then do.
Tag-restricted users see tag-scoped results from list endpoints throughout the API; that filtering is described per resource in later chapters and does not change how you authenticate.
Treat every token as a password. Prefer the narrowest scope set a client needs rather than the full eight. Prefer browser sign-in (OAuth) for interactive clients so no long-lived secret is copied around. Set an expiry on PATs, and revoke any token you are no longer using — the DELETE /api/tokens/{id} and POST /api/oauth/token/revoke endpoints below exist for exactly this.
Signing in
Sign in and obtain a session token
POST/api/login
Permission none (public endpoint)
Authenticates a user with email and password and returns a bearer access token. This is the path the web UI uses for itself; it also establishes a browser web-guard session when called with cookies. For scripts, prefer a personal access token — this endpoint is throttled to 10 requests per minute and its token is tied to the SPA’s per-browser revocation behavior.
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
email | string | yes | Username or email. Max 255. |
password | string | yes | The account password. |
totp | string | no | One-time MFA code. Required only on the second call when the first returns TOTP Required (see below). |
browser_id | string | no | Per-browser token name in the form spa:<uuid>:<fp>. The SPA sets this; omit it and the token is named authToken. |
Response 200 (success — note the status field carries 201)
{
"status": 201,
"hasError": false,
"user": { "id": 7, "email": "operator@example.com", "name": "Operator" },
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOi…"
}
Response 200 (MFA required — resubmit with totp)
{ "status": false, "hasError": true, "message": "TOTP Required" }
Errors
| Status | Body | When |
|---|---|---|
200 | {"status":false,"hasError":true,"message":"Invalid Username/Password"} | Bad credentials or invalid TOTP. The status field, not the HTTP code, signals failure. |
422 | {"message":…,"errors":{…}} | email or password missing. |
429 | rate-limit response | More than 10 attempts in a minute. |
Login does not use HTTP status to signal application errors — a failed sign-in still returns HTTP 200 with hasError: true. Read the body’s status/hasError fields, not the HTTP code.
Example
curl -sS -X POST https://APPLIANCE/api/login \
-H "Content-Type: application/json" \
-d '{"email":"operator@example.com","password":"s3cret"}'
Sign out
POST/api/logout
Permission any authenticated user
Revokes the access token used to make the call and tears down the companion web-guard session. After this returns, the token is dead.
Response 200
{ "message": "You have been successfully logged out." }
Example
curl -sS -X POST https://APPLIANCE/api/logout -H "Authorization: Bearer $TOKEN"
Rehydrate the browser session
POST/api/ensureWebSession
Permission any authenticated user
A helper used by the web UI: given a valid bearer token, it re-establishes the browser’s web-guard session cookie so a server-rendered consent page (such as /auth/authorize) recognizes the user without a second login. Bodyless; integrators rarely call it directly.
Response 200
{ "status": true }
Errors
| Status | Body | When |
|---|---|---|
401 | {"status":false,"message":"unauthenticated"} | No valid token. |
500 | {"status":false,"message":"no session context"} | Called without a session-capable request context. |
Server information
POST/api/information
Permission none (public endpoint)
Returns the appliance hostname and whether a PHP session is present. Useful as a reachability probe before authenticating. Bodyless.
Response 200
{
"status": 201,
"hasError": false,
"data": { "hostname": "netmon-prod", "sessionId": false }
}
Example
curl -sS -X POST https://APPLIANCE/api/information
Personal access tokens
Personal access tokens are the recommended credential for scripts and automation. You mint one, the plaintext is shown once, and you store it like any other secret. Tokens default to a 365-day lifetime and carry the full eight-scope set unless you narrow either at creation. All three routes are gated by permission:api and operate only on the calling user’s own tokens.
Two name prefixes are reserved and rejected at creation: spa: (the web UI’s per-browser token namespace) and MCP Client: (the prefix OAuth flows stamp on issued tokens). Choose any other name.
List your tokens
GET/api/tokensapi
Lists the calling user’s tokens. Recently-revoked rows (created within the last 30 days) are included so the UI can show history; older revoked rows are hidden. The plaintext token value is never returned by this endpoint — only by store.
Response 200
{
"tokens": [
{
"id": 42,
"name": "nightly-report-runner",
"origin": "pat",
"client_id": null,
"scopes": ["mcp:devices", "mcp:reports"],
"revoked": false,
"expires_at": "2027-06-15 12:00:00",
"created_at": "2026-06-15 12:00:00",
"last_used_at": "2026-06-15 13:04:11"
}
]
}
The origin field is oauth for tokens minted through an OAuth flow (name begins MCP Client: ) and pat for everything else.
Errors
| Status | Body | When |
|---|---|---|
500 | {"error":"Failed to load tokens"} | Unexpected server error. |
Example
curl -sS https://APPLIANCE/api/tokens -H "Authorization: Bearer $TOKEN"
Mint a personal access token
POST/api/tokensapi
Creates a new token and returns its plaintext value in the response. Save it now — it is never shown again.
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
name | string | yes | 1–100 chars. Must not start with spa: or MCP Client: . |
expires_in_days | integer | no | 1–3650. Defaults to 365. |
scopes | array | no | Subset of the eight mcp:* scopes. An empty or omitted array defaults to the full set. |
scopes.* | string | — | Each must be one of mcp:devices, mcp:alerts, mcp:logs, mcp:reports, mcp:system, mcp:tools, mcp:vne, mcp:capture. |
Response 201
{
"id": 43,
"name": "nightly-report-runner",
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOi…",
"expires_at": "2027-06-15T12:00:00+00:00",
"created_at": "2026-06-15T12:00:00+00:00"
}
Errors
| Status | Body | When |
|---|---|---|
400 | {"errors":{"name":["…reserved prefix…"]}} | Name uses a reserved prefix. |
400 | {"errors":{…}} | Validation failed (bad name length, out-of-range expiry, unknown scope). |
500 | {"error":"Failed to create token"} | Unexpected server error. |
Example
curl -sS -X POST https://APPLIANCE/api/tokens \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"name":"nightly-report-runner","expires_in_days":730,"scopes":["mcp:devices","mcp:reports"]}'
Revoke one of your tokens
DELETE/api/tokens/{id}api
Revokes a token you own. The ownership check is built in — you can only revoke your own tokens through this route.
Path parameters
| Name | Type | Notes |
|---|---|---|
id | integer | Token id from the listing. |
Response 200
{ "revoked": true }
Errors
| Status | Body | When |
|---|---|---|
404 | {"error":"Token not found"} | No token with that id is owned by you. |
500 | {"error":"Failed to revoke token"} | Unexpected server error. |
Example
curl -sS -X DELETE https://APPLIANCE/api/tokens/43 \
-H "Authorization: Bearer $TOKEN"
Administering other users’ tokens
These three routes let an administrator audit and revoke tokens belonging to other users — for credential rotation or when an account is compromised. They live under permission:users (an sa or users holder) rather than permission:api, so an admin can manage tokens without personally holding the API gate.
List another user’s tokens
GET/api/users/{userId}/tokensusers
Same shape and filtering as GET /api/tokens, but for the user named in the path.
Path parameters
| Name | Type | Notes |
|---|---|---|
userId | integer | The target user’s id. |
Response 200 — identical {"tokens":[…]} envelope as the self-listing.
Errors
| Status | Body | When |
|---|---|---|
403 | {"error":"Forbidden"} | Caller lacks sa/users. |
404 | {"error":"User not found"} | No such user. |
500 | {"error":"Failed to load tokens"} | Unexpected server error. |
Revoke one of another user’s tokens
DELETE/api/users/{userId}/tokens/{id}users
Revokes a single token belonging to the named user. The revoking admin’s id is recorded for audit.
Path parameters
| Name | Type | Notes |
|---|---|---|
userId | integer | The token owner’s id. |
id | integer | Token id. |
Response 200
{ "revoked": true }
Errors
| Status | Body | When |
|---|---|---|
403 | {"error":"Forbidden"} | Caller lacks sa/users. |
404 | {"error":"Token not found"} | No matching token for that user. |
This immediately invalidates the target user’s token. Any client using it starts failing on the next call.
Revoke all of a user’s tokens
POST/api/users/{userId}/tokens/revoke-allusers
The kill switch: revokes every live token for the named user in one shot. Used when an account is compromised or being deprovisioned. Returns the count of tokens newly revoked (already-revoked rows are skipped). Bodyless.
Path parameters
| Name | Type | Notes |
|---|---|---|
userId | integer | The target user’s id. |
Response 200
{ "revoked": 3, "user_id": 12 }
Errors
| Status | Body | When |
|---|---|---|
403 | {"error":"Forbidden"} | Caller lacks sa/users. |
404 | {"error":"User not found"} | No such user. |
Every active token for the user is revoked at once, including their web UI session. They will need to sign in again.
OAuth 2.0 device-code flow
The device-code flow (RFC 8628) is how a non-browser client — the PowerShell module, the Wireshark extcap satellite, an MCP client — gets a token when its user must consent in a browser. The client asks for a device_code and a short user_code, displays the user_code and a verification URL to the user, and polls while the user approves in any browser. These endpoints are public but rate-limited (30 requests/minute on the /api/oauth/* group). The browser consent pages live under /auth/device.
The full round trip:
- Client
POSTs to/api/oauth/device/authorize, receivesdevice_code,user_code, andverification_uri. - Client tells the user to open
verification_uriand enteruser_code. - User (signed in, holding
permission:api) approves at/auth/device. - Client polls
/api/oauth/device/tokenwith thedevice_codeeveryintervalseconds until it gets an access token.
Request a device code
POST/api/oauth/device/authorize
Permission none (public endpoint)
Starts the flow. Returns the codes and the verification URL the client shows to the user.
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
client_name | string | yes | Printable ASCII, max 100. Shown to the user on the consent page (e.g. powershell-host). |
scopes | array | no | Subset of the eight mcp:* scopes. Defaults to ["mcp:devices"]. |
Response 200
{
"device_code": "Hq3…40chars…",
"user_code": "ABCD1234",
"verification_uri": "https://APPLIANCE/auth/device",
"verification_uri_complete": "https://APPLIANCE/auth/device?user_code=ABCD1234",
"expires_in": 600,
"interval": 5
}
Example
curl -sS -X POST https://APPLIANCE/api/oauth/device/authorize \
-H "Content-Type: application/json" \
-d '{"client_name":"powershell-host","scopes":["mcp:devices","mcp:logs"]}'
Poll for the token
POST/api/oauth/device/token
Permission none (public endpoint)
Exchanges an approved device_code for an access token. Until the user has approved, this returns authorization_pending — keep polling at the interval returned above. The code is single-use: once it yields a token it is consumed.
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
device_code | string | yes | The device_code from the authorize call. |
Response 200
{
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOi…",
"refresh_token": "…",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "mcp:devices mcp:logs"
}
Errors
| Status | Body | When |
|---|---|---|
400 | {"error":"authorization_pending","error_description":"…"} | User has not approved yet. Keep polling. |
400 | {"error":"expired_token","error_description":"…"} | The device code expired or was already used. |
Example
curl -sS -X POST https://APPLIANCE/api/oauth/device/token \
-H "Content-Type: application/json" \
-d '{"device_code":"Hq3…40chars…"}'
Device consent page
GET/auth/device
Permission browser web session (not a bearer token)
The HTML page where a signed-in user enters the user_code to approve a device. Accepts an optional ?user_code= query parameter to pre-fill the field (used by verification_uri_complete). If the browser has no web-guard session, it redirects to the SPA login with a ?redirect= back to this URL. This page is opened in a browser by a human, not called by an API client.
Approve a device
POST/auth/device
Permission browser web session (not a bearer token); user must hold api or sa
The form submission from the consent page. Validates that the signed-in user holds permission:api, looks up the user_code, mints the token, and stores it against the device_code so the polling client can collect it. Renders a success page on approval. Submitted by the browser, not by an API client.
Request body (form-encoded)
| Field | Type | Required | Notes |
|---|---|---|---|
user_code | string | yes | Exactly 8 characters. |
Errors — re-renders the form with an error message when the user lacks permission:api, the code is invalid/expired, or the code was already used. The message is intentionally identical across the permission-denied and invalid-code cases so it cannot be used to probe for active codes.
Device-code input helper script
GET/auth/device-code.js
Permission none (public endpoint)
Serves a small same-origin JavaScript asset that auto-formats the user_code input on the consent page (uppercases, strips non-alphanumerics). It exists as an external file so the SPA’s strict script-src 'self' CSP allows it. Not an API endpoint — the consent page loads it.
Response 200 — Content-Type: application/javascript, cached for one day.
OAuth 2.1 authorization-code flow with PKCE
The authorization-code flow (per the MCP 2025-03-26 spec) is the browser redirect flow used by clients that can host a redirect URI — typically a loopback http://localhost:… or an HTTPS callback. PKCE with the S256 method is mandatory: the client generates a code_verifier, sends its SHA-256 challenge to /auth/authorize, and proves possession of the verifier at /auth/token. Redirect URIs must be localhost/loopback or HTTPS.
A client may pre-register through Dynamic Client Registration (below) to obtain a client_id, or use a known one. Registered clients must be approved by an operator before they can complete the flow.
Authorization request (consent page)
GET/auth/authorize
Permission browser web session (not a bearer token); user must hold api or sa
The browser endpoint a client redirects the user to. It validates the request parameters, ensures the user is signed in (bouncing to SPA login with ?redirect= if not), checks permission:api, confirms the client is approved, and renders the consent page listing the requested scopes. This URL is opened in the user’s browser, not called by the API client directly.
Query parameters
| Name | Type | Required | Notes |
|---|---|---|---|
response_type | string | yes | Must be code. |
client_id | string | yes | The client identifier. |
redirect_uri | string | yes | Must be localhost/loopback or HTTPS, and match the client’s registered URIs if registered. |
code_challenge | string | yes | PKCE challenge (base64url of SHA256(code_verifier)). |
code_challenge_method | string | no | Defaults to S256 (the only fully supported method). |
scope | string | no | Space-delimited mcp:* scopes. Defaults to mcp:devices. Unknown scopes are rejected. |
state | string | no | Opaque value round-tripped back to the redirect URI. |
Errors
| Status | Body | When |
|---|---|---|
400 | {"error":"unsupported_response_type",…} | response_type is not code. |
400 | {"error":"invalid_request",…} | Missing client_id/redirect_uri/code_challenge, or a bad redirect-URI scheme/host. |
400 | {"error":"invalid_scope",…} | A requested scope is not in the canonical eight. |
403 | consent page (auth.denied) | User lacks permission:api. |
403 | consent page (auth.pending) | Client is registered but not yet operator-approved. |
Authorization approval
POST/auth/authorize
Permission browser web session (not a bearer token); user must hold api or sa
The consent form submission. On approval it mints a one-time authorization code (10-minute expiry) and redirects the browser to the client’s redirect_uri with code and state. On denial — or any failed gate — it redirects with an OAuth error parameter instead. Submitted by the browser.
Request body (form-encoded)
| Field | Type | Required | Notes |
|---|---|---|---|
client_id | string | yes | Must be an approved registered client. |
redirect_uri | string | yes | http/https; matched against the cached request. |
code_challenge | string | yes | PKCE challenge. |
code_challenge_method | string | yes | Must be S256. |
scope | string | yes | Space-delimited mcp:* scopes. |
state | string | no | Round-tripped to the callback. |
deny | any | no | If present, the user denied; redirect carries error=access_denied. |
Response — 302 redirect to redirect_uri?code=…&state=… on approval, or redirect_uri?error=…&state=… on denial / a failed permission or approval gate.
Token exchange
POST/auth/token
Permission none (this endpoint is the auth mechanism); throttled 30/minute
Exchanges an authorization code for an access token, verifying PKCE. Also handles grant_type=refresh_token by delegating to the refresh path. The authorization code is consumed atomically (no replay). For confidential clients (token_endpoint_auth_method=client_secret_post), the client_secret is also verified.
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
grant_type | string | yes | authorization_code or refresh_token. |
code | string | for code grant | The authorization code from the redirect. |
redirect_uri | string | for code grant | http/https; must match the code’s stored value. |
client_id | string | for code grant | Must match the code’s client. |
code_verifier | string | for code grant | The PKCE verifier; its S256 hash must equal the stored challenge. |
client_secret | string | confidential clients only | Verified for client_secret_post clients. |
refresh_token | string | for refresh grant | See /api/oauth/token/refresh. |
Response 200
{
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOi…",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "…",
"scope": "mcp:devices mcp:logs"
}
Errors
| Status | Body | When |
|---|---|---|
400 | {"error":"unsupported_grant_type",…} | grant_type is neither supported value. |
400 | {"error":"invalid_grant",…} | Code invalid/expired, client_id or redirect_uri mismatch, PKCE verification failed, or user not found. |
401 | {"error":"invalid_client",…} | A confidential client’s client_secret is wrong. |
403 | {"error":"access_denied",…} | The user no longer holds permission:api. |
Example (authorization-code grant)
curl -sS -X POST https://APPLIANCE/auth/token \
-H "Content-Type: application/json" \
-d '{"grant_type":"authorization_code","code":"…","redirect_uri":"http://localhost:7777/callback","client_id":"…","code_verifier":"…"}'
Dynamic client registration
POST/auth/register
Permission none (public, per RFC 7591); throttled 10/minute
Registers an OAuth client and returns a generated client_id (and a client_secret for confidential clients). Registration is open per RFC 7591, but a freshly-registered client cannot complete an authorization-code flow until an operator approves it (operators review the pending queue under permission:system). Redirect URIs must be localhost/loopback or HTTPS. The appliance caps total registered clients at 500.
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
client_name | string | yes | Printable ASCII, max 100. |
redirect_uris | array | no | Each must be http(s) and localhost/loopback or HTTPS. |
grant_types | array | no | Each of authorization_code, refresh_token. Defaults to both. |
response_types | array | no | Each must be code. Defaults to ["code"]. |
token_endpoint_auth_method | string | no | none (public) or client_secret_post (confidential). Defaults to none. |
Response 201
{
"client_id": "a1b2c3d4-…-uuid",
"client_name": "My MCP Client",
"redirect_uris": ["http://localhost:7777/callback"],
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"],
"token_endpoint_auth_method": "none"
}
For confidential clients the response also carries client_secret and client_secret_expires_at: 0 (never expires). The secret is shown once.
Errors
| Status | Body | When |
|---|---|---|
400 | {"error":"invalid_redirect_uri",…} | A redirect URI is neither localhost nor HTTPS. |
422 | {"message":…,"errors":{…}} | Validation failed (bad name, scheme, grant/response type). |
503 | {"error":"server_error",…} | The 500-client cap is reached. |
Example
curl -sS -X POST https://APPLIANCE/auth/register \
-H "Content-Type: application/json" \
-d '{"client_name":"My MCP Client","redirect_uris":["http://localhost:7777/callback"]}'
Managing tokens (introspect, refresh, revoke)
These endpoints live under the /api/oauth/* group (throttled 30/minute) and let a client validate, renew, and discard the tokens it holds.
Introspect a token
POST/api/oauth/token/introspect
Permission none (public endpoint)
Validates a token and returns its metadata, in the RFC 7662 shape. Active tokens also have their last_used_at bumped as a side effect. An invalid, revoked, or expired token simply returns {"active": false}.
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
token | string | yes | The bearer token (JWT) to inspect. |
Response 200 (active)
{
"active": true,
"client_id": "a1b2c3d4-…",
"username": "operator@example.com",
"sub": 7,
"scope": "mcp:devices mcp:logs",
"exp": 1781000000
}
Response 200 (inactive)
{ "active": false }
Refresh a token
POST/api/oauth/token/refresh
Permission none (public endpoint)
Exchanges a valid refresh token for a new access token and a new refresh token. The refresh token is rotated — the one you submit is revoked, and the old access token it was bound to is revoked too. (grant_type=refresh_token at /auth/token routes here.)
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
refresh_token | string | yes | A live, unexpired refresh token. |
Response 200
{
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOi…",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "…new…",
"scope": "mcp:devices mcp:logs"
}
Errors
| Status | Body | When |
|---|---|---|
400 | {"error":"invalid_grant",…} | Refresh token invalid, expired, already used, or its user is gone. |
Example
curl -sS -X POST https://APPLIANCE/api/oauth/token/refresh \
-H "Content-Type: application/json" \
-d '{"refresh_token":"…"}'
Revoke a token
POST/api/oauth/token/revoke
Permission any authenticated user
Revokes the supplied token and any refresh tokens bound to it. The caller must be authenticated and must either own the token or hold sa; revoking someone else’s token without sa is refused.
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
token | string | yes | The bearer token (JWT) to revoke. |
Response 200
{ "revoked": true }
Errors
| Status | Body | When |
|---|---|---|
400 | {"revoked":false} | The token couldn’t be parsed or was already inactive. |
403 | {"revoked":false} | Caller neither owns the token nor holds sa. |
The token and its refresh tokens are immediately invalidated.
Example
curl -sS -X POST https://APPLIANCE/api/oauth/token/revoke \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"token":"eyJ0eXAiOiJKV1QiLCJhbGciOi…"}'
Discovery documents
Standards-based OAuth clients configure themselves from these two well-known documents rather than hardcoding endpoint URLs. Both are public, served on the appliance origin, and require no token.
Authorization server metadata
GET/.well-known/oauth-authorization-server
Permission none (public endpoint)
RFC 8414 authorization-server metadata. An MCP or OAuth client reads this to discover the authorize, token, and registration endpoints, the supported grant and challenge methods, and the scope list.
Response 200
{
"issuer": "https://APPLIANCE",
"authorization_endpoint": "https://APPLIANCE/auth/authorize",
"token_endpoint": "https://APPLIANCE/auth/token",
"registration_endpoint": "https://APPLIANCE/auth/register",
"response_types_supported": ["code"],
"grant_types_supported": ["authorization_code", "refresh_token"],
"code_challenge_methods_supported": ["S256"],
"token_endpoint_auth_methods_supported": ["none", "client_secret_post"],
"scopes_supported": [
"mcp:devices", "mcp:alerts", "mcp:logs", "mcp:reports",
"mcp:system", "mcp:tools", "mcp:vne", "mcp:capture"
]
}
Protected resource metadata
GET/.well-known/oauth-protected-resource
Permission none (public endpoint)
RFC 9728 protected-resource metadata. It points clients at the MCP resource (/mcp) and the authorization server that issues tokens for it, and lists the supported scopes.
Response 200
{
"resource": "https://APPLIANCE/mcp",
"authorization_servers": ["https://APPLIANCE"],
"bearer_methods_supported": ["header"],
"scopes_supported": [
"mcp:devices", "mcp:alerts", "mcp:logs", "mcp:reports",
"mcp:system", "mcp:tools", "mcp:vne", "mcp:capture"
],
"resource_documentation": "https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization"
}