Client Login
The One Portal supports three login paths for client users: direct email/password login, Hub SSO (for PSA-provisioned users), and Portal SSO.
Direct Login
The default login method. A client user navigates to the portal URL, enters their email and password, and receives a session cookie.
Login URL: https://app.theoneportal.app/portal/login?tenant=YOUR_SLUG
Session behavior:
- Session cookie name:
portal_session - Expiry: 24 hours
- Cookie is HttpOnly and Secure; set on the portal domain
- Each login increments
login_countand updateslast_login_at
First-time login after invite:
- The MSP admin creates a client user account in Admin → Clients
- The client receives an invitation email with a tokenized link
- The link opens the portal's
/portal/register?invite=TOKENpage - The client sets their password and is immediately logged in
Hub SSO Login
Clients provisioned through PSA's portal-access feature can log in without a password using a signed JWT generated by Hub.
How it works:
- MSP enables "portal access" for a contact in The One PSA
- The client receives a login link containing a short-lived JWT (
70-secondexpiry) - The client clicks the link, which hits
/api/auth/client-hub-sso?token=TOKEN - The Portal API validates the JWT:
- Verifies the signature using
PORTAL_CLIENT_SSO_SECRET - Confirms
context === 'client_portal' - Checks the JTI (JWT ID) against a replay-prevention store — each token can only be used once
- Verifies the signature using
- The API auto-provisions a client user record if one does not already exist, linking
hub_user_id,psa_contact_id, andpsa_company_id - A Portal session cookie is set and the client is redirected to
/portal/tickets
JWT claims carried through:
| Claim | Description |
|---|---|
sub | Hub user ID (becomes the Portal user ID) |
email | User email address |
tenant_id | MSP tenant ID |
tenant_slug | MSP tenant slug |
client_id | PSA company ID (maps to Portal client ID) |
access_level | own / company / company_admin |
psa_contact_id | PSA contact record ID |
psa_company_id | PSA company record ID |
Hub SSO tokens expire after 70 seconds. If the client clicks the link after it has expired, they will see an "Invalid or expired SSO token" error. Have them request a new link from the MSP.
Access Levels
Client users have one of two roles, set during provisioning:
| Role | Permissions |
|---|---|
user | View own tickets, invoices, documents, announcements, team, profile |
company_admin | All of the above, plus view all company tickets, manage team members |
The access_level from PSA (own / company / company_admin) maps to the Portal role:
company_admin→company_adminroleownorcompany→userrole
Session Management
- Duration: 24 hours from login
- Logout: Click the logout icon in the top-right header. The session cookie is cleared.
- Expired sessions: If the session cookie expires mid-session, the next API call returns 401 and the user is redirected to the login page.
- Multiple sessions: Not limited — a user can have multiple active sessions on different browsers/devices.
Password Reset
- Navigate to the login page
- Click Forgot password?
- Enter your email address and submit
- Receive a reset email with a time-limited link
- Click the link →
/portal/reset-password?token=TOKEN - Enter and confirm your new password
- Log in with the new password
Password reset is only applicable for direct-login accounts. Hub SSO users do not have a portal password — they always authenticate through Hub.
Troubleshooting Login Issues
| Error | Cause | Resolution |
|---|---|---|
| "Invalid credentials" | Wrong email or password | Reset password or contact MSP to verify account exists |
| "Invalid or expired SSO token" | Hub SSO link clicked after 70-second window | Request a new login link from the MSP |
| "Token already used" | SSO link clicked twice | Request a new login link — JTI replay prevention blocked the second attempt |
| "Client SSO not configured" | Missing PORTAL_CLIENT_SSO_SECRET env var | MSP admin must configure the env var on the Portal API function app |
| Login succeeds but portal is blank | Wrong tenant slug in URL | Verify the URL includes ?tenant=YOUR_SLUG |