Skip to content

Session View Switch — FE Guide (2026-08-03, updated 2026-08-04)

Multi-role users (teacher+admin, teacher+department head, teacher/staff+referent, …) can switch their session to a single "view", and the backend genuinely narrows their permissions to it. This is what powers the role selector: a teacher-admin who switches to the Teacher view takes attendance as a plain teacher (own-group fences, teacher defaults), not as an admin.

2026-08-04 update — view selection at login. The login flow now asks the user to pick their view straight away (§6). Unlike the original mid-session switch (which was additive), the login change is a coordinated behavior change — read §6 before shipping anything against login.

1. Discovery — GET /auth/me

The user object gains two fields:

{
  "user": {
    // ...existing fields (activeProfile, availableProfiles, roles, ...)...
    "activeView": null,          // string | null — null = combined default session
    "availableViews": [          // only on /auth/me (like availableProfiles)
      { "key": "teacher",         "label": { "en_US": "Teacher", "it_IT": "Docente" } },
      { "key": "referent",        "label": { "en_US": "Referent", "it_IT": "Referente" } },
      { "key": "admin",           "label": { "en_US": "Administrator", "it_IT": "Amministratore" } },
      { "key": "department_head", "label": { "en_US": "Department Principal", "it_IT": "Preside del dipartimento" } }
    ]
  }
}
  • availableViews is ONE flat list — person profiles and roles share the namespace, deduped (a teacher never sees two "teacher" entries). Labels are display-ready and use the exact same copy as GET /roles; custom tenant roles appear with their stored name echoed in both languages.
  • activeView tells you which entry is currently active. null = the combined default (full union of the user's grants). Referent/student sessions report their profile key ("referent" / "student").
  • activeView is present on every auth response (login, refresh, switch-profile, /auth/me); availableViews only on /auth/me.
  • Single-view users (just a teacher, just a referent) get a one-entry list — hide the selector for them if you like.
  • Whether to render an explicit "All roles" entry for the combined default is FE copy: the backend has no all key; that state is target: null / activeView: null.

2. Switching — POST /auth/switch-view

POST /api/v1/auth/switch-view
{ "target": "teacher" }        // a key from availableViews
{ "target": null }             // back to the combined default

The 200 response body is the recompiled permissions map, byte-shape-identical to GET /permissions. Replace your cached permissions with it directly — no second round-trip. Everything you already key on /permissions (nav on empty scopes, field visibility, action buttons) keeps working unchanged; it simply reflects the selected view now.

  • On a real switch the auth cookies are rotated in the response (Set-Cookie) — same mechanics as switch-profile. Subsequent requests automatically run under the narrowed session.
  • Idempotent: switching to the view you're already on returns the permissions map without rotating cookies.
  • Cross-boundary jumps work in one call: sitting in the Referent view and clicking "Administrator" is a single switch-view — the backend handles the profile change internally.
  • Selecting teacher while the user is also a department head narrows to the teacher role ONLY — the department-head widening (department boards, wider rosters) genuinely disappears until they switch back.
  • After a switch, re-fetch /auth/me if you display activeView/roles anywhere (the switch response carries only the permissions map).
  • Rate limit: 10 req / 60s.

3. Errors

Status Code When FE handling
400 ACTIVE_VIEW_NOT_AVAILABLE switch-view target not in the user's views, or a login pick not offered / no longer available (e.g. the role was just revoked) Mid-session: re-fetch /auth/me, re-render the selector. At login: restart from the password step to get a fresh chooser
401 AUTH_REFRESH_MISSING Refresh cookie absent Treat as logged out
401 AUTH_TOKEN_INVALID / AUTH_TOKEN_REUSE Stale/replayed refresh token Force re-login (existing handling)

Errors carry the standard envelope with localized messages.

4. Sessions can silently return to the default

If a role is revoked while the user sits in that view, the next token refresh does not log them out — the session falls back to the combined default (activeView: null, full remaining grants). Detect it via activeView on the refresh response (or /auth/me) and update the selector; permissions should be re-fetched when you observe the view changed under you.

5. What did NOT change

  • GET /permissions — identical shape and behavior.
  • availableProfiles + POST /auth/switch-profile — untouched; profile switching still works exactly as before (a profile switch resets any active view to the default). You can now drive both axes from the single switch-view endpoint if you prefer one code path.
  • Single-view users — zero behavioral change anywhere: no chooser, straight login, activeView as before.

6. Login flow — view selection at login (2026-08-04, BEHAVIOR CHANGE)

Coordinated change, not additive. Three things about login behave differently from one deploy to the next. Ship the FE handling together with the backend deploy.

What changed

The management display labels are now HR Manager, Front Office, and Department Principal (localized by the existing label catalogue). Role keys remain hr, secretary, and department_head; persist and submit keys, never labels.

  1. The chooser fires for more users. The login requiresProfileSelection prompt now appears for any account with two or more views — including single-profile multi-role users (every admin, every department head) who previously logged straight in. The response shape is the one you already handle; only the trigger widened.
  2. A pick narrows. Picking an entry at login has exactly the switch-view semantics: the session starts in that view (activeView set, permissions already narrowed — no post-login switch needed). Previously, picking the Teacher profile landed in the combined session; now it lands in roles: ['teacher']. There is no "all my roles" entry at login — the union is one POST /auth/switch-view { target: null } away.
  3. The rejection code changed. A bad/stale pick on POST /auth/login/select-profile is now 400 ACTIVE_VIEW_NOT_AVAILABLE (was ACTIVE_PROFILE_NOT_AVAILABLE).

The prompt payload

ProfileSelectionResponseDto gains views — render this list; it is the complete chooser (including the platform entry for platform admins):

{
  "requiresProfileSelection": true,
  "profiles": ["teacher"],           // LEGACY — kept for transition, will be dropped later
  "views": [
    { "key": "teacher",         "label": { "en_US": "Teacher", "it_IT": "Docente" } },
    { "key": "admin",           "label": { "en_US": "Administrator", "it_IT": "Amministratore" } },
    { "key": "department_head", "label": { "en_US": "Department Principal", "it_IT": "Preside del dipartimento" } }
  ],
  "selectionToken": "eyJ..."
}

POST the chosen key back on the existing endpoint and field:

POST /api/v1/auth/login/select-profile
{ "selectionToken": "eyJ...", "activeProfile": "department_head" }

The 200 response is the usual AuthUserDto with cookies — user.activeView reflects the pick, user.roles is already narrowed.

  • Labels are the same strings as availableViews / GET /roles; the platform entry is { "key": "platform", "label": { "en_US": "Platform administration", "it_IT": "Amministrazione piattaforma" } }.
  • A 400 ACTIVE_VIEW_NOT_AVAILABLE on a pick means the entry was never offered or stopped being available while the chooser was open (10-minute token) — the selection token itself is still valid for a different pick, but the simplest handling is to restart login for a fresh chooser.
  • Single-view users never see the chooser and land in the combined default exactly as before.