Enabling Authentication

How to turn on login for a Manzano project: when you need auth, how it interacts with your routes, how to read identity in Grove actions, and how to log users out.

Prerequisites: Authentication Overview, a deployed Manzano project. What you'll learn: The decision of whether to enable auth, the steps to configure it, how route protection works today, and how to pair it with authorization.

Status: Preview. The end-to-end configuration surface described on this page is firming up. Where the live surface differs from what is documented here, the authoritative references are your manzano deploy output and the Identity Providers page. Expect the rough edges to smooth over the next few releases.


When to enable authentication

Start a project without auth. Most Grove work — writing actions, modeling records, running workflows — does not need an authenticated caller. Add authentication when you need to:

If none of those apply yet, skip this page. ctx.principal will be null in your actions, and manzano dev will serve every route to any caller.

How login works at a glance

When authentication is enabled on a project:

  1. An anonymous request to a protected route is redirected by the edge to the configured identity provider.
  2. The user signs in (OIDC provider, built-in email magic link, etc.).
  3. The provider redirects back with a signed token. The edge validates the token, extracts claims, and stamps them onto the request as HTTP headers (X-Sub, X-Email, …).
  4. The edge signs the whole request with an HMAC shared secret and forwards it to your backend.
  5. Grove reads the identity headers, populates ctx.principal, and runs your action.

See Identity Flow for the full trust-boundary picture.

All-or-nothing today

When auth is configured on a project, every dynamic route becomes protected. Static assets (files under apps/<app>/static/ and the like) remain public; anything that hits a Grove handler requires an authenticated caller.

Per-route opt-in (for example a [[protect]] directive that lets you mark specific routes as public while keeping the rest authenticated) is planned but not yet shipped. In the meantime, two patterns cover most needs:

Picking a provider

Two flavors of identity provider are supported:

See Identity Providers for per-provider setup and claim-mapping details.

Reading identity in a Grove action

Once authentication is enabled and a user is signed in, every action receives a populated ctx.principal:

action post_comment(ctx, body: String) {
  if ctx.principal is null {
    fail "Sign in to comment"
  }

  let comment = Comment {
    id: Id.generate(),
    author_id: ctx.principal.id,
    author_email: ctx.principal.claims.email,
    body: body,
    created_at: ctx.now,
  }

  emit CommentPosted { comment: comment }
}

For deeper authorization beyond "is the user signed in," use an authorize hook or a Cedar policy — see Authorization Hooks and Per-Project Cedar Policy.

Anonymous access

You can still permit anonymous callers on specific actions even with auth enabled. The most straightforward approach is a Cedar policy that targets Role::"__anonymous":

// Anyone can read published posts.
permit (
  principal in Role::"__anonymous",
  action == Action::"read_post",
  resource
)
when { resource.status == "published" };

In an authorize hook, the equivalent pattern is:

authorize read_post(ctx, post_id: Id) {
  let post = lookup_post(post_id)
  if post.status == "published" {
    return                        // permit, no further checks
  }
  if ctx.principal is null {
    fail "Sign in to view drafts"
  }
  if post.author_id != ctx.principal.id {
    fail "Only the author can view drafts"
  }
}

See Authorization Hooks for the full hook model.

Logout

Manzano's edge exposes a logout endpoint for authenticated apps:

Your Grove code does not need to implement logout. If you want a "Sign out" button in your UI, link it to /auth/logout.

Troubleshooting

SymptomLikely cause
Request 302s to /auth/login even after signing inID-token cookie not being sent. Check cookie domain matches the request host.
Signed-in user shows ctx.principal == null in backendToken validated at the edge but your action ran before the edge request reached it (cached preview URL, misrouted request). Re-deploy and reload.
Infinite redirect loop after sign-inProvider's configured redirect URL doesn't match the tenant's allowed list. See Identity Providers.
"X-Forwarded-Host missing" errorA request bypassed the edge. Only Manzano-hosted deployments are supported; hitting a backend directly won't pass origin-auth HMAC.

For deeper debugging, the per-layer table in Identity Flow — Debugging authenticated requests maps symptoms to layers.

See Also