Skip to main content
Version 0.4Draft

Token introspection

What token introspection does

Token introspection lets a resource server determine whether a token is currently active and retrieve its metadata, without validating a JWT signature locally.

The LuxID introspection endpoint implements RFC 7662 - OAuth 2.0 Token Introspection (opens in a new tab).

Endpoint:

https://login.luxid.lu/mga/sps/oauth/oauth20/introspect

Method: POST

Not available for Partner use

Although the discovery document advertises an introspection_endpoint, token introspection is not supported for Partners today. Testing against UAT returned an HTML error page (HTTP 400) instead of an RFC 7662 JSON response. Do not build against it.

LuxID issues JWT access and ID tokens, so validate tokens locally instead: verify the RS256 signature against the JWKS endpoint (opens in a new tab) and check the exp, iss, and aud claims. See Token validation issues. The remainder of this page is retained as reference; if you have a genuine need for server-side introspection, contact LuxID.

Client authentication is required - an unauthenticated introspection request is rejected with 401 Unauthorized. See Client authentication below.


When to use introspection

Introspection is useful when:

  • Your resource server receives an opaque access token (a token that is not a self-contained JWT and cannot be validated locally).
  • You need to verify a refresh token before attempting a silent renewal - confirming it has not been revoked before making a call to the Token endpoint.
  • You run a polyglot environment where implementing full JWT validation in every language/service is not practical.
  • You need the current metadata of a token (remaining TTL, scope, subject) in addition to its validity.

Introspection is a network hop - it adds latency and creates a dependency on the LuxID server for every token check. If your access tokens are JWTs, prefer local validation using the JWKS endpoint for the common path; reserve introspection for cases where local validation is insufficient (for example, checking whether a token has been explicitly revoked).

See Advanced security options for guidance on combining local JWT validation with selective introspection.


Client authentication

All introspection requests must be authenticated by the calling application. LuxID supports the following client authentication methods:

MethodDescription
client_secret_basicHTTP Basic Auth: Authorization: Basic base64(client_id:client_secret)
client_secret_postInclude client_id and client_secret in the POST body

Use the authentication method configured for your application in the LuxID Console.


Request

POST /mga/sps/oauth/oauth20/introspect HTTP/1.1
Host: login.luxid.lu
Content-Type: application/x-www-form-urlencoded
Authorization: Basic base64(client_id:client_secret)

token=<access_or_refresh_token>&token_type_hint=access_token

Request parameters

ParameterRequiredDescription
tokenYesThe token to introspect (access token or refresh token)
token_type_hintNoaccess_token or refresh_token - helps the server look up the token faster; ignored if incorrect

cURL examples

Using HTTP basic auth

curl -s \
-X POST \
-H "Content-Type: application/x-www-form-urlencoded" \
-u "your_client_id:your_client_secret" \
-d "token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...&token_type_hint=access_token" \
https://login.luxid.lu/mga/sps/oauth/oauth20/introspect

Using client_secret_post

curl -s \
-X POST \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..." \
-d "token_type_hint=access_token" \
-d "client_id=your_client_id" \
-d "client_secret=your_client_secret" \
https://login.luxid.lu/mga/sps/oauth/oauth20/introspect

Introspecting a Refresh Token

curl -s \
-X POST \
-H "Content-Type: application/x-www-form-urlencoded" \
-u "your_client_id:your_client_secret" \
-d "token=dGhpcyBpcyBhIHJlZnJlc2ggdG9rZW4...&token_type_hint=refresh_token" \
https://login.luxid.lu/mga/sps/oauth/oauth20/introspect

Response - active token

{
"active": true,
"scope": "openid profile email",
"client_id": "your_client_id",
"username": "jean.dupont@example.lu",
"token_type": "Bearer",
"exp": 1716378000,
"iat": 1716374400,
"nbf": 1716374400,
"sub": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"aud": "your_client_id",
"iss": "https://login.luxid.lu",
"jti": "7f3e9b1d-2a4c-4d5e-8f6a-1b2c3d4e5f6a"
}

Response fields

FieldTypeDescription
activebooleantrue if the token is valid and has not expired or been revoked
scopestringSpace-separated list of scopes associated with the token
client_idstringThe application that was issued the token
usernamestringHuman-readable identifier for the user (email address)
token_typestringBearer for access tokens
expintegerUnix timestamp at which the token expires
iatintegerUnix timestamp at which the token was issued
nbfintegerUnix timestamp before which the token is not valid
substringSubject - the user's stable LuxID identifier
audstringAudience - the intended recipient of the token
issstringIssuer - https://login.luxid.lu
jtistringJWT ID - unique identifier for this token

Response - inactive token

{
"active": false
}

An active: false response means the token is expired, revoked, or was never valid. No other fields are returned for inactive tokens - per RFC 7662 §2.2, implementations MUST NOT return additional metadata for inactive tokens to avoid leaking information.

Do not distinguish between "expired" and "revoked" in your application logic - treat both as "token not usable" and respond accordingly.


Error responses

401 unauthorized - client authentication failed

{
"error": "invalid_client",
"error_description": "Client authentication failed"
}

Resolution: verify your client_id and client_secret are correct and match what is configured in the LuxID Console.

400 bad request - missing token parameter

{
"error": "invalid_request",
"error_description": "The request is missing the required parameter: token"
}

Resolution: include the token parameter in the POST body.


Performance and caching

Introspection adds a round-trip to the LuxID server on every call. For high-throughput resource servers, this can become a bottleneck. Mitigate with short-lived caching:

  • Cache key: the token string itself (or a hash of it).
  • Cache TTL: the lesser of your chosen cache duration and the token's remaining lifetime (exp - now). A 30-second TTL is a reasonable starting point for access tokens; adjust based on your acceptable staleness.
  • Invalidation: do not serve cached active: true responses after the token's exp timestamp. Always check exp before returning a cached result.
  • Negative caching: cache active: false responses for a short period (for example, 10 seconds) to avoid hammering the endpoint with invalid tokens.
Do not cache introspection results across users or across client applications

The introspection response is specific to the token and to the calling client.

Caching example

def introspect_with_cache(token, cache, luxid_client):
cache_key = "introspect:" + sha256(token)
cached = cache.get(cache_key)

if cached is not None:
# Check that the cached result has not expired
if cached["active"] and cached["exp"] > time.now():
return cached
elif not cached["active"]:
return cached # Negative cache hit

result = luxid_client.introspect(token)
ttl = min(30, max(0, result.get("exp", 0) - time.now()))
cache.set(cache_key, result, ttl=ttl)
return result

Resource server integration pattern

A typical resource server validates an incoming Bearer token as follows:


Introspection vs local JWT validation

Local JWT validationIntrospection
Network callNo - validates locally using cached JWKSYes - calls LuxID server
Revocation awarenessNo - cannot detect explicit revocationYes - reflects current state
LatencyLowHigher (network round-trip)
Best forCommon path in high-throughput APIsRevocation checks, opaque tokens
Recommended pattern

Validate the JWT signature locally (cache the JWKS with a reasonable TTL). Use introspection only when you need to confirm the token has not been explicitly revoked, or when the token is opaque.


Updated 2026-07-03