Protect an API
An API must validate every bearer token before it trusts claims or performs protected work. Guardhouse SDKs support local JWT signature validation and remote introspection.
Choose a Validation Mode
| Mode | How it works | Prefer it when |
|---|---|---|
| JWT signature | The API validates the token locally with Guardhouse signing keys | Low latency and availability without a validation request per call are the priority |
| Introspection | The API asks /connect/introspect whether the token is active | Immediate server-side token state and centralized validation are more important than an extra network dependency |
Local JWT validation remains valid until token expiration unless the API applies another revocation strategy. Introspection requires confidential client credentials and makes Guardhouse availability part of the request path. Choose deliberately for each API.
Required Checks
Whichever mode you choose, require:
- the canonical tenant issuer
- the audience registered for this API resource
- a valid token lifetime
- a valid signature or active introspection result
- the scope, role, permission, or policy needed by the operation
Decoding a JWT payload is not validation. Never trust claims read from an unverified token.
ASP.NET Core
Use the official Guardhouse.SDK package for local validation:
using Guardhouse.SDK.Extensions;
using Guardhouse.SDK.Models;
builder.Services.AddGuardhouseResource(options =>
{
options.Authority = "https://your-tenant.guardhouse.cloud";
options.Audience = "orders_api";
options.ValidationMode = TokenValidationMode.JwtSignature;
});
builder.Services.AddAuthorization();
Then place authentication before authorization in the pipeline and protect the endpoint with the Guardhouse scheme:
app.UseAuthentication();
app.UseAuthorization();
app.MapGet(
"/api/orders",
[Microsoft.AspNetCore.Authorization.Authorize(AuthenticationSchemes = "Guardhouse")]
() => Results.Ok());
For introspection:
builder.Services.AddGuardhouseResource(options =>
{
options.Authority = "https://your-tenant.guardhouse.cloud";
options.Audience = "orders_api";
options.ValidationMode = TokenValidationMode.Introspection;
options.IntrospectionClientId = "YOUR_INTROSPECTION_CLIENT_ID";
options.IntrospectionClientSecret = "YOUR_INTROSPECTION_CLIENT_SECRET";
options.IntrospectionCredentialTransmission =
IntrospectionCredentialTransmission.FormData;
});
Store introspection credentials outside source control. See the .NET quickstart and .NET SDK reference.
Express
Use @guardhouse/node for local JWT validation and optional scope enforcement:
import { guardhouseMiddleware } from "@guardhouse/node";
app.use(
"/api/orders",
guardhouseMiddleware({
authority: "https://your-tenant.guardhouse.cloud",
audience: "orders_api",
requiredScopes: ["orders.read"],
}),
);
Switch the same middleware to introspection when required:
guardhouseMiddleware({
authority: "https://your-tenant.guardhouse.cloud",
audience: "orders_api",
validationMode: "introspection",
introspectionClientId: process.env.GUARDHOUSE_INTROSPECTION_CLIENT_ID,
introspectionClientSecret: process.env.GUARDHOUSE_INTROSPECTION_CLIENT_SECRET,
});
See the Node.js quickstart.
Authorization and Responses
Authentication middleware establishes a verified principal. Apply authorization at every protected endpoint or route group:
- Return
401 Unauthorizedfor a missing, malformed, expired, or otherwise invalid token. - Return
403 Forbiddenwhen the token is valid but lacks required access. - Do not rely on a hidden button or protected frontend route to secure a backend operation.
- Keep CORS configuration separate from authentication and authorization.
Verification Checklist
Test the API with:
- no
Authorizationheader - a malformed bearer token
- an expired token
- a valid token from the wrong issuer
- a valid token for the wrong audience
- a valid token missing the required scope or policy
- a valid token with the required access
Only the final case should reach the protected operation.