Skip to main content

SSO and Authentication

Hub GUI handles all authentication via the SSOController at /login. It supports username/password login, OAuth providers, account registration, password reset, account activation, invitation links, and two-factor authentication (2FA).

SSO Flow Overview​

User visits Hub GUI
│
├── Has auth cookie? ──→ Validate token ──→ Redirect to /main or /dashboard
│
└── No cookie ──→ Show SSOPage
│
├── /login, /signIn → SignInPanel (email + password + OAuth)
├── /signUp, /invite → SignUpPanel (registration form)
├── /resetPassword → ResetPasswordPanel (email input)
├── /resetPassword/{token} → NewPasswordPanel (new password form)
├── /accountActivation/{token} → Activate + redirect to SignIn
├── /twoFactorChallenge → TwoFactorChallengePanel (6-digit code)
└── /logout, /signOut → Log out + redirect to /

SSOController​

The controller registers all auth routes via @Resolver:

@Resolver("/login", [
"/logout", "/signOut",
"/signIn", "/signUp",
"/resetPassword", "/resetPassword/{token}",
"/accountActivation", "/accountActivation/{token}",
"/invite", "/invite/{token}",
"/twoFactorChallenge"
])
export class SSOController extends BasePageLayoutController<SSOPage> { }

Route Handling (processRequestPath)​

RouteAction
/twoFactorChallengeCalls ssoBinding.ProcessOneTimeCode() to validate 2FA
/logout, /signOutCalls auth.LogOut(), fetches /api/sso/logout, shows message, redirects to /
/login, /signInSelects SignIn tab
/resetPassword (no token)Selects ResetPassword tab
/resetPassword/{token}Selects NewPassword tab (token extracted from URL)
/accountActivation/{token}Calls auth.ActivateUser(token), shows result toast
/signUp, /inviteSelects SignUp tab (via default tab matching)

On afterLoad(), the controller checks for an existing auth cookie. If found and valid:

  1. Sets the token on AuthManagerConnector
  2. Reloads UserContext
  3. Redirects admins to /dashboard, others to /main

Session Token Mechanics​

Hub sessions are backed by one TokenPayload document per user (tokenPayload collection: Owner, optional Expire in ms). The session token string is deterministic: base64(header).base64(userId).HMAC, where the HMAC is keyed by the payload's DB id. Consequences:

  • A second login on another device returns the same token string — multi-device sessions share one payload. authManagerWorker.multiSessionEnabled: true makes login paths skip tokens.Clear, so the shared payload survives; clearing it (or re-creating it) rotates the payload id and instantly invalidates the user's tokens everywhere.
  • WebTokenManager.Create($id, $noExpire, $expireTime, $transient): reuses the existing payload; sets Expire only when currently empty (never shrinks or extends). A $transient mint (10 s SSO handoff, 5 s agent ticket) never touches the payload's Expire — stamping a short window onto a no-expire payload used to kill every live session of the user.
  • WebTokenManager.Prolong($id, $seconds) extends Expire to at least NOW + $seconds (extend-only); the sliding idle prolong in Validate uses the same rule (NA-242).
  • Expiry is enforced only via payload.Expire — the expire value inside the token header is informational. Client-side the token lives in localStorage with no client expiry.
  • "Keep me logged in": classic LogIn and the SSO /authenticate resolver both issue a bounded trustedDevice.sessionDuration session (90 d default) when the user opts in, otherwise the session.idleTimeout sliding window (7 d default). ExchangeToken honors multiSessionEnabled exactly like classic LogIn.
  • Logout clears the shared payload, so logging out on one device logs out all devices (by design).

SSO Panels​

SSOPage extends BasePageLayout and defines five tab panels:

Tab IDPanelDefaultRoutes
signInSignInPanelYes/login, /signIn, /accountActivation/{token}
signUpSignUpPanel/signUp, /invite/{token}
resetPasswordResetPasswordPanel—
newPasswordNewPasswordPanel—
twoFactorChallengeTwoFactorChallengePanel/twoFactorChallenge

SignInPanel​

  • Fields: email, password, "keep logged in" checkbox
  • Actions: Sign in button, "Sign in with Google" (OAuth, shown if enabled in config)
  • On success: Redirects to /dashboard (admin) or /main (user)
  • On failure: Shows validation toast, marks invalid fields

SignUpPanel​

  • Fields: username, email, first name, last name, password
  • Actions: Register button
  • On success: Shows activation email notification, switches to SignIn tab

ResetPasswordPanel​

  • Fields: email
  • Actions: Reset button → sends password reset email

NewPasswordPanel​

  • Fields: new password (with confirmation)
  • Actions: Update button → confirms reset with token from URL

TwoFactorChallengePanel​

  • Fields: 6-digit code input, auth code panel (QR code for 2FA setup)
  • Actions: Validate button → verifies code via auth.Validate2FA()
  • On success: Sets TwoFAChecked(true), redirects to /main

SSOBinding​

SSOBinding is the core authentication logic handler. It connects UI controls to AuthManagerConnector methods.

Key Methods​

MethodPurpose
Bind($userControls, $handlers)Wires sign-in form — Enter key and submit button trigger ProcessLogIn()
ProcessLogIn($userControls, $handlers)Validates inputs, calls auth.LogIn(), stores token, handles credential persistence
BindTwoFA($userControls, $handlers)Wires 2FA form — validates 6-digit code format, calls auth.Validate2FA()
BindAuthCode($userControls, $handlers)Wires auth code panel for QR-based 2FA setup
ProcessOneTimeCode()Handles 2FA challenge flow — validates code or redirects to registration
TwoFAChecked($value?)Gets/sets 2FA completion flag in browser persistence
LastLoginUrl($value?)Gets/sets last URL before auth redirect (for post-login navigation)

Login Flow​

  1. User enters email + password
  2. SSOBinding.ProcessLogIn() calls AuthManagerConnector.LogIn()
  3. On success: token stored via CurrentToken(), TwoFAChecked reset
  4. Browser credential persistence triggered (form with autocomplete attributes)
  5. If 2FA enabled → redirects to /twoFactorChallenge
  6. Otherwise → redirects to referer URL or main page

2FA Flow​

  1. After login, UserContext.Load() checks auth.Has2FAActivated()
  2. If 2FA active and not yet checked → BasePageLayoutController redirects to /twoFactorChallenge
  3. User enters 6-digit code → SSOBinding.BindTwoFA() validates format
  4. auth.Validate2FA(code) verifies with backend
  5. On success: Need2FACheck = false, TwoFAChecked(true) persisted, redirect to /main
  6. On failure: error toast, code field marked invalid

OAuth Integration​

OAuthBinding handles OAuth provider sign-in (currently Google). It is initialized with a provider name and wired to the sign-in panel's Google button.

this.oauthBinding = new OAuthBinding("google");

OAuth providers are configured in IProject.authProviders. The sign-in panel shows/hides provider buttons based on the enabled flag:

$instance.signIn.oauth2Container.Visible(activeProviders > 0);
$instance.signIn.signInGoogleButton.Visible(providers.google?.enabled);

Role-Based Access (UserContext)​

UserContext is the central authentication state object, accessed via Loader.getInstance().getContext().

Properties​

PropertyTypeSource
IsAuthenticatedbooleanauth.IsAuthenticated()
IsAdminbooleanauth.HasGroup(systemGroups.Admin)
IsSysAdminbooleanauth.HasGroup(systemGroups.SysAdmin)
IsActivebooleanuser.Activated
Need2FACheckbooleanauth.Has2FAActivated() && !sso.TwoFAChecked()
IsMobilebooleanRequest user agent detection
IsIOSbooleanRequest user agent detection
HasAdminRightsbooleanComputed: IsAuthenticated && (IsAdmin || IsSysAdmin)

Key Methods​

MethodPurpose
Load()Fetches auth state, user profile, groups, 2FA status
Reload()Resets and re-loads all auth state
Restore()Resets all flags to false, clears user
SwitchToProfile($group)Switches between Admin/SysAdmin roles
getUserProfile()Returns current User model
getCurrentToken()Returns current auth token

Profile Switching​

Admins can switch between Admin and SysAdmin profiles at runtime:

await context.SwitchToProfile(groups.SysAdmin);
// context.IsSysAdmin === true, context.IsAdmin === false

This changes which dashboard tools are visible without re-authentication.

Error and Auth Resolvers​

The HttpProcessor/Resolvers/ directory contains two specialized resolvers:

  • ErrorResolver — Handles unrecoverable errors, shows error page
  • NotAuthorizedResolver — Triggers when accessValidator() fails for authenticated but unauthorized users

See also: Hub GUI Overview | Pages and Routing | Dashboard and Tools Menu | Authentication