Auth

Scoped Auth

Fine-grained permission scopes for commands

Scoped Auth#

Scoped auth lets you attach permission scopes to tokens and require specific scopes on individual commands. Use scopedVerifier to map tokens to scopes, and requiredScopes on commands to enforce them.

typescript
import { createSurf, scopedVerifier } from '@surfjs/core'
ย 
const surf = await createSurf({
name: 'My API',
ย 
// scopedVerifier maps tokens โ†’ their allowed scopes
authVerifier: scopedVerifier({
'sk-readonly-token': ['catalog:read'],
'sk-admin-token': ['catalog:read', 'catalog:write', 'orders:write'],
}),
ย 
commands: {
// Public โ€” no auth needed
search: {
description: 'Search products',
params: { query: { type: 'string', required: true } },
run: async ({ query }) => db.search(query),
},
ย 
// Requires auth + specific scope
'product.create': {
description: 'Create a product',
auth: 'required',
requiredScopes: ['catalog:write'],
params: { name: { type: 'string', required: true } },
run: async ({ name }, ctx) => db.products.create({ name }),
},
ย 
// Orders scope โ€” returns 403 if token lacks 'orders:write'
'order.create': {
description: 'Create an order',
auth: 'required',
requiredScopes: ['orders:write'],
run: async (params, ctx) => db.orders.create(ctx.claims!.token, params),
},
},
})

๐Ÿ’ก Tip: Scopes are checked after token validity โ€” a token must be valid AND have all requiredScopes or the request returns 403. Use ctx.scopes in your handler to inspect which scopes the token carries.

For fine-grained access control with JWT tokens:

typescript
import { createSurf, scopedVerifier } from '@surfjs/core'
ย 
const surf = await createSurf({
name: 'My API',
auth: { type: 'bearer', description: 'OAuth2 Bearer token with scopes' },
ย 
// scopedVerifier checks requiredScopes against token scopes
authVerifier: scopedVerifier(async (token) => {
const decoded = await verifyJWT(token)
if (!decoded) return { valid: false, reason: 'Invalid token' }
return {
valid: true,
claims: { userId: decoded.sub },
scopes: decoded.scopes, // e.g. ['read:products', 'write:orders']
}
}),
ย 
commands: {
// Requires specific scopes
'order.create': {
description: 'Create an order',
auth: 'required',
requiredScopes: ['write:orders'],
run: async (params, ctx) => orders.create(ctx.claims!.userId, params),
},
ย 
// Multiple required scopes (all must be present)
'admin.analytics': {
description: 'View analytics dashboard',
auth: 'hidden',
requiredScopes: ['read:analytics', 'admin'],
run: async () => getAnalytics(),
},
},
})

When a token lacks a required scope, Surf returns a 403 AUTH_FAILED response with details about the missing scopes. Scopes are also advertised in the manifest so agents know what permissions are needed.