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)
| Route | Action |
|---|---|
/twoFactorChallenge | Calls ssoBinding.ProcessOneTimeCode() to validate 2FA |
/logout, /signOut | Calls auth.LogOut(), fetches /api/sso/logout, shows message, redirects to / |
/login, /signIn | Selects 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, /invite | Selects SignUp tab (via default tab matching) |
Auth Cookie Check
On afterLoad(), the controller checks for an existing auth cookie. If found and valid:
- Sets the token on
AuthManagerConnector - Reloads
UserContext - 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: truemakes login paths skiptokens.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; setsExpireonly when currently empty (never shrinks or extends). A$transientmint (10 s SSO handoff, 5 s agent ticket) never touches the payload'sExpire— stamping a short window onto a no-expire payload used to kill every live session of the user.WebTokenManager.Prolong($id, $seconds)extendsExpireto at leastNOW + $seconds(extend-only); the sliding idle prolong inValidateuses the same rule (NA-242).- Expiry is enforced only via
payload.Expire— theexpirevalue inside the token header is informational. Client-side the token lives inlocalStoragewith no client expiry. - "Keep me logged in": classic
LogInand the SSO/authenticateresolver both issue a boundedtrustedDevice.sessionDurationsession (90 d default) when the user opts in, otherwise thesession.idleTimeoutsliding window (7 d default).ExchangeTokenhonorsmultiSessionEnabledexactly like classicLogIn. - 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 ID | Panel | Default | Routes |
|---|---|---|---|
signIn | SignInPanel | Yes | /login, /signIn, /accountActivation/{token} |
signUp | SignUpPanel | /signUp, /invite/{token} | |
resetPassword | ResetPasswordPanel | — | |
newPassword | NewPasswordPanel | — | |
twoFactorChallenge | TwoFactorChallengePanel | /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
| Method | Purpose |
|---|---|
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
- User enters email + password
SSOBinding.ProcessLogIn()callsAuthManagerConnector.LogIn()- On success: token stored via
CurrentToken(),TwoFACheckedreset - Browser credential persistence triggered (form with autocomplete attributes)
- If 2FA enabled → redirects to
/twoFactorChallenge - Otherwise → redirects to referer URL or main page
2FA Flow
- After login,
UserContext.Load()checksauth.Has2FAActivated() - If 2FA active and not yet checked →
BasePageLayoutControllerredirects to/twoFactorChallenge - User enters 6-digit code →
SSOBinding.BindTwoFA()validates format auth.Validate2FA(code)verifies with backend- On success:
Need2FACheck = false,TwoFAChecked(true)persisted, redirect to/main - 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
| Property | Type | Source |
|---|---|---|
IsAuthenticated | boolean | auth.IsAuthenticated() |
IsAdmin | boolean | auth.HasGroup(systemGroups.Admin) |
IsSysAdmin | boolean | auth.HasGroup(systemGroups.SysAdmin) |
IsActive | boolean | user.Activated |
Need2FACheck | boolean | auth.Has2FAActivated() && !sso.TwoFAChecked() |
IsMobile | boolean | Request user agent detection |
IsIOS | boolean | Request user agent detection |
HasAdminRights | boolean | Computed: IsAuthenticated && (IsAdmin || IsSysAdmin) |
Key Methods
| Method | Purpose |
|---|---|
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 pageNotAuthorizedResolver— Triggers whenaccessValidator()fails for authenticated but unauthorized users
See also: Hub GUI Overview | Pages and Routing | Dashboard and Tools Menu | Authentication