GuidesOAuth Apps
Verify Tokens and Operate OAuth Clients
Verify OAuth access tokens in your resource server, rotate client secrets, and revoke grants safely.
Verify Tokens and Operate OAuth Clients
After consent and token issuance, your backend must verify every access token before serving protected resources.
This page focuses on runtime operations and post-launch controls.
Verify OAuth token in resource server
import { WachtClient } from "@wacht/backend";
const client = new WachtClient({
apiKey: process.env.WACHT_BACKEND_API_KEY!,
});
export async function verifyOAuthRequest(token: string, method: string, resource: string) {
const authz = await client.gateway.checkPrincipalAuthz({
principalType: "oauth_access_token",
principalValue: token,
method,
resource,
requiredPermissions: ["mcp:invoke"],
});
return {
clientId: authz.headers["x-wacht-oauth-client-id"],
grantedResource: authz.headers["x-wacht-granted-resource"],
oauthResource: authz.metadata?.oauth_resource,
scopes: authz.metadata?.scopes || [],
expiresAt: authz.metadata?.expires_at,
};
}Verify OAuth token in Rust resource server
use wacht::{WachtClient, WachtConfig};
use wacht::gateway::{GatewayAuthzOptions, GatewayPrincipalType};
let api_key = std::env::var("WACHT_BACKEND_API_KEY").expect("WACHT_BACKEND_API_KEY is required");
let frontend_host = std::env::var("WACHT_FRONTEND_HOST").expect("WACHT_FRONTEND_HOST is required");
let client = WachtClient::new(WachtConfig::new(api_key, frontend_host)).expect("valid Wacht config");
let authz = client
.gateway()
.check_authz_with_principal_type(
GatewayPrincipalType::OauthAccessToken,
access_token,
"POST",
"mcp",
GatewayAuthzOptions {
required_permissions: Some(vec!["mcp:invoke".to_string()]),
..Default::default()
},
)
.await?;
if !authz.allowed {
// Reject request in your framework middleware/handler.
}
let principal = authz.resolve_principal_context();
let scopes = principal.metadata.scopes;
let granted_resource = principal.metadata.granted_resource;Runtime endpoint controls to expose in your app integrations
From each OAuth app issuer domain:
POST /oauth/token(code exchange and refresh)POST /oauth/introspect(token state and claims)POST /oauth/revoke(token invalidation)POST /oauth/registerandGET|PUT|DELETE /oauth/register/{client_id}(if dynamic registration is enabled)
What operators can control after launch
From backend management APIs/console:
- Rotate client secrets:
POST /deployments/{deployment_id}/oauth/apps/{oauth_app_slug}/clients/{oauth_client_id}/rotate-secret - Deactivate clients
- Revoke grants for a specific client/user grant
- Disable app (
is_active=false) - Archive/unarchive scopes and update scope mapping metadata
For MCP resource servers
This is the same verification model to use for protected machine endpoints:
- Use
mcp-authmiddleware. - Verify bearer token with
gateway.checkPrincipalAuthz. - Pass resolved subject/scopes into tool handlers.
Operational playbook
Rotate client secret
Use your OAuth client rotation flow and deploy secret updates atomically.
const rotated = await client.oauth.rotateOAuthClientSecret(
"mcp-auth",
oauthClient.id,
);Persist the new secret immediately; Wacht only returns secret material at creation/rotation time.
Revoke grants
When users disconnect integrations or incidents happen, revoke active grants for the target client.
client.oauth()
.revoke_oauth_grant("mcp-auth", &oauth_client.id, grant_id)
.send()
.await?;Audit and monitoring
Track:
- token verification failures by client ID,
- denied scope attempts,
- grant revocations,
- secret rotation timestamp and actor.