Skip to main content
View as Markdown

Post-Login Landing

After sign-in, different users should land on different pages — an admin on the admin home, a customer on their own record. Sovrium expresses this as auth configuration, not as redirect logic scattered across pages: each role declares a defaultLanding, the auth block declares a landingPath mount point, and noAccessPath catches whoever matches nothing.

app.yaml
auth:
  strategies:
    - type: emailAndPassword
  scopeTables: [clients]
  defaultRole: customer-admin
  landingPath: /portal
  noAccessPath: /403
  roles:
    - name: engineer
      defaultLanding: /admin
    - name: customer-admin
      defaultLanding: /portal/clients/$currentUser.assignments.clients[0]
      pickerLanding: /portal/select/clients

Configuration Fields

Two fields on auth, two on each role:

Field Level Description
landingPath auth Engine-resolver mount path. Sessions navigating here are redirected to the matching role's defaultLanding. Must start with /. Required whenever any role sets defaultLanding or pickerLanding.
noAccessPath auth Fallback path when no role's defaultLanding matches the session. Must start with /. Defaults to /403.
defaultLanding roles[] Per-role landing URL. Must start with /. May contain at most one $currentUser.assignments.<table>[0] token.
pickerLanding roles[] Multi-record fallback for a templated defaultLanding. Must start with /. Must not contain any assignment token.

Three cross-field rules are checked when the config is decoded, so a half-wired landing setup fails sovrium validate rather than misrouting at runtime:

  • auth.landingPath must be set once any role declares defaultLanding or pickerLanding.
  • A role with pickerLanding must also declare defaultLanding — the picker is a fallback, not a landing in its own right.
  • That defaultLanding must be templated. A pickerLanding beside a bare URL can never fire, so it is rejected rather than left as dead config.

Resolution Logic

When an authenticated session navigates to auth.landingPath, the engine walks auth.roles[] in declaration order and applies the first role the user holds. Order is therefore meaningful: declare the most specific role first.

defaultLanding shape Assignment count for the templated <table> Engine action
Bare URL (no token) n/a Redirect to defaultLanding unconditionally.
Templated (one token) Exactly one assignment Substitute the assignment ID, redirect there.
Templated (one token) More than one assignment Redirect to the role's pickerLanding.
No role matched / no assignments n/a Redirect to noAccessPath (defaults to /403).

$currentUser.assignments Interpolation

Moved to Assignments & Landing Guards.

The landingPath Page Is the Access Guard

Moved to Assignments & Landing Guards.

Full Example

app.yaml
auth:
  strategies:
    - type: emailAndPassword
  scopeTables: [clients]
  defaultRole: customer-admin
  landingPath: /portal
  noAccessPath: /403
  roles:
    - name: engineer
      defaultLanding: /admin
    - name: customer-admin
      defaultLanding: /portal/clients/$currentUser.assignments.clients[0]
      pickerLanding: /portal/select/clients

pages:
  - name: portal-landing
    path: /portal
    access: { require: authenticated, redirectTo: /login }
    components: []
  - name: client-detail
    path: /portal/clients/:id
    access: authenticated
    components: []
  - name: client-picker
    path: /portal/select/clients
    access: authenticated
    components: []
  - name: admin-home
    path: /admin
    access: [engineer]
    components: []
  - name: forbidden
    path: /403
    access: authenticated
    components: []

In this app an engineer lands on /admin; a customer-admin with one client lands on that client's detail page; with several, on the picker; and anyone the resolver cannot place hits /403.

  • Assignments & Landing Guards — the interpolation token and the guard page.
  • Roles & RBAC — where defaultLanding / pickerLanding are declared.
  • SessionsscopeTables, user_access, and $currentUser.activeAssignment.
  • Pages — page path, access, and dataSource.filter.

Last updated August 11, 2026

This documentation was written with AI, so errors or outdated content are possible. Sovrium is in beta. Contributions and corrections are welcome.

Built with Sovrium