GuidesOAuth Apps
Create OAuth Apps and Clients
Set up OAuth apps, scopes, and clients in a way that is safe for production SaaS deployments.
Create OAuth Apps and Clients
Use this flow for provider setup before any consent or token exchange happens.
OAuth app/client creation is available through backend deployment-scoped APIs and both SDKs.
Step 1: Create OAuth app in Console
In Console OAuth settings, create the app with:
- Stable app
slug(avoid renaming later). - User-facing app name and description.
- Verified domain/FQDN.
- Supported scopes and scope definitions.
Step 2: Add scopes with clear contracts
Good scope design is specific and additive:
agents:runfor agent execution only.mcp:invokefor MCP tool invocation.workspace:readfor workspace data reads.
Avoid broad scopes like * or ambiguous names.
Step 3: Create OAuth clients
Per environment (dev/staging/prod), create separate OAuth clients:
- Register exact redirect URIs.
- Use least privilege scopes.
- Store client secret only in server-side secret managers.
What is configurable from Console or backend APIs
OAuth app settings
slug(create-time identity; use a stable value)name,description, optionallogosupported_scopesandscope_definitions(including category and permission mappings)allow_dynamic_client_registrationis_active
OAuth app domain behavior
- Production deployments require an explicit
fqdnat creation. - Non-production deployments auto-generate an OAuth domain.
- Domain verification uses
POST /deployments/{deployment_id}/oauth/apps/{oauth_app_slug}/verify-domain. fqdnis create-time; app updates do not includefqdn.
OAuth client settings
client_auth_method:client_secret_basic,client_secret_post,client_secret_jwt,none,private_key_jwtgrant_types: supportsauthorization_codeandrefresh_tokenredirect_uris- optional metadata:
client_name,client_uri,logo_uri,tos_uri,policy_uri,contacts,software_id,software_version - key material for
private_key_jwt: exactly one ofjwks_uri,jwks,public_key_pem token_endpoint_auth_signing_alg
Hard validation rules to design for
authorization_codeis required for OAuth clients.client_credentialsis currently disabled.- If
authorization_codeis enabled, at least oneredirect_uriis required. - Redirect URIs must be valid
http/httpsURLs with no fragment. - For
private_key_jwt, exactly one key material source is required on create. jwks_urimust usehttpsforprivate_key_jwt.jwks_uri/jwks/public_key_pemare rejected for non-private_key_jwtclients.
Create via SDK
import { WachtClient } from "@wacht/backend";
const client = new WachtClient({
apiKey: process.env.WACHT_BACKEND_API_KEY!,
});
const app = await client.oauth.createOAuthApp({
slug: "mcp-auth",
name: "MCP Auth App",
description: "OAuth provider for MCP access",
fqdn: "auth.example.com",
supported_scopes: ["mcp:invoke", "workspace:read"],
});
const oauthClient = await client.oauth.createOAuthClient("mcp-auth", {
client_auth_method: "client_secret_basic",
grant_types: ["authorization_code", "refresh_token"],
redirect_uris: ["https://app.example.com/oauth/callback"],
client_name: "MCP Auth Web Client",
});use wacht::models::{CreateOAuthAppRequest, CreateOAuthClientRequest};
let app = client.oauth()
.create_oauth_app(
CreateOAuthAppRequest {
slug: "mcp-auth".to_string(),
name: "MCP Auth App".to_string(),
description: Some("OAuth provider for MCP access".to_string()),
fqdn: Some("auth.example.com".to_string()),
supported_scopes: Some(vec!["mcp:invoke".to_string(), "workspace:read".to_string()]),
scope_definitions: None,
allow_dynamic_client_registration: Some(false),
logo_file: None,
logo_filename: None,
},
)
.send()
.await?;
let oauth_client = client.oauth()
.create_oauth_client(
"mcp-auth",
CreateOAuthClientRequest {
client_auth_method: "client_secret_basic".to_string(),
grant_types: vec!["authorization_code".to_string(), "refresh_token".to_string()],
redirect_uris: vec!["https://app.example.com/oauth/callback".to_string()],
client_name: Some("MCP Auth Web Client".to_string()),
client_uri: None,
logo_uri: None,
tos_uri: None,
policy_uri: None,
contacts: None,
software_id: None,
software_version: None,
token_endpoint_auth_signing_alg: None,
jwks_uri: None,
jwks: None,
public_key_pem: None,
},
)
.send()
.await?;Update existing app/client settings
Use these updates instead of creating new app slugs for every config change.
await client.oauth.updateOAuthApp("mcp-auth", {
name: "MCP Auth App",
description: "OAuth provider for MCP access",
allow_dynamic_client_registration: false,
is_active: true,
});
await client.oauth.updateOAuthClient("mcp-auth", oauthClient.id, {
redirect_uris: [
"https://app.example.com/oauth/callback",
"https://staging.app.example.com/oauth/callback",
],
grant_types: ["authorization_code", "refresh_token"],
});SDK operations to use
Use these SDK operations in automation flows:
client.oauth.listOAuthApps()client.oauth.createOAuthApp(request)client.oauth.updateOAuthApp(oauthAppSlug, request)client.oauth.createOAuthClient(oauthAppSlug, request)client.oauth.updateOAuthClient(oauthAppSlug, oauthClientId, request)client.oauth.rotateOAuthClientSecret(oauthAppSlug, oauthClientId)
Use direct backend endpoints only when SDKs are not available in your runtime.
Production checklist
- Different client IDs per environment.
- Redirect URIs pinned and reviewed.
- Secret rotation policy documented.
- Scope names and descriptions versioned in docs.
- Deprovisioning flow exists for leaked client secrets.