Skip to main content

Access policy and default role

The Access tab decides what a brand-new user gets the first time they log in to use your MCP server. Plus an exceptions table for the people who need more (or less) than the default.

What the UI does

Two sections:

  1. Default access. A checkbox to enable the policy, and a dropdown to pick the role granted to first-time users. Trigger is fixed at first_successful_login.
  2. Manual exceptions. A table of user-to-role assignments specific to this resource server. Each row carries a SOURCE tag — MANUAL_ADMIN for ones an admin added by hand, DEFAULT_POLICY for ones the policy created automatically.

The right-hand Policy preview panel shows what the role will grant: tool counts, scope counts, default role grants, viewer scopes.

Data model

Two tables back this:

resource_server_access_policies (one row per RS, unique on resource_server_id):

ColumnWhat it does
enabledIf false, the policy is dormant. New users get nothing.
default_role_idFK to the role granted on first login.
assignment_triggerAlways first_successful_login today.
assignment_sourcedefault_policy — distinguishes auto-grants from manual ones.

role_bindings (the join table where the actual user-role assignments live):

ColumnWhat it does
user_id, role_id, resource_server_idThe triple that identifies a grant.
assignment_sourcedefault_policy if created by the access policy on first login; manual if added by an admin.
assignment_metadataJSONB. Stores who created the row, when, and any audit notes.

The assignment_source distinction matters because it tells you which bindings will survive policy changes (manual ones) versus which will be re-created on each first login (default_policy ones).

How the trigger fires

When a user authenticates against the AuthSec backend for the first time, the backend checks every resource_server_access_policies row with enabled = true. For each one, it creates a role_bindings row with assignment_source = 'default_policy'. The user is now permanently bound to that role for that RS — even if the policy is later disabled, the binding persists.

In other words: the policy is a creation rule, not an ongoing membership check. Disable the policy and existing users keep their bindings; new users start getting nothing.

Adding a manual exception

Use the Manual exceptions table → Add. Pick a user and a role. The row goes into role_bindings with assignment_source = 'manual'.

Manual rows take precedence over default policy: if a user has both a manual binding and a default-policy binding, the union of their scopes is what the SDK sees.

Removing a binding

Click the trash icon on a row. The row is hard-deleted from role_bindings. If you delete a default-policy binding for a user who logs in again, the policy will re-create it (assuming the policy is still enabled). To prevent re-creation, either disable the policy or create a manual binding with a no-scope role.

Roles and scopes — where they connect

Roles are containers for scopes. The viewer role auto-created on registration starts empty — you add scopes to it from the role editor. Scopes themselves come from the Define scopes step.

The runtime flow:

  1. User logs in → access policy grants them viewer role.
  2. User authorizes an OAuth client, consents to scope rs-xyz:tools:read.
  3. The issued token's scope claim is the intersection of (what the role grants) and (what the user consented to).
  4. The Go SDK validates the token, sees scope=rs-xyz:tools:read, allows tools mapped to that scope.

This means the role gates what the user can request; consent gates what they actually got.

What's not here today

The plan calls these out explicitly — they're roadmap, not bugs:

  • Per-scenario simulation. "If user X uses client Y to call tool Z, what happens?" The Access tab can preview policy state, but not interactively simulate per-request. The backend endpoint for this isn't built yet.
  • Bulk import for manual exceptions. Adding 50 users from a CSV — one at a time today.
  • Time-bound exceptions. Exceptions don't expire. If you want to grant temporary access, you have to remember to delete the row.

Auth scheme

EndpointAuth
GET /authsec/resource-servers/:id/access-policyAdmin JWT
PUT /authsec/resource-servers/:id/access-policyAdmin JWT
POST /authsec/resource-servers/:id/role-bindings (manual)Admin JWT
DELETE /authsec/resource-servers/:id/role-bindings/:user_idAdmin JWT

None of these are SDK-facing.