Single sign-on
Single sign-on changes how people log in to your public board. Instead of the default UserJot login flow, users who click Sign In are sent to your own authentication page. After you authenticate them, you send them back to UserJot with a signed JWT.
In UserJot, SSO is a board login mode: it replaces the normal board login rather than adding a separate button. It is separate from automatic login, which carries widget-identified users into your public pages from your app.
Enable SSO
Open Settings in your workspace, then go to Login.
Under Authentication, set Board login to SSO. The first time you do this, an Enable SSO dialog asks for your redirect URL.
Enter your SSO redirect URL and confirm with Enable SSO. You can change it later from the SSO redirect URL section on the same page.
Copy your Project ID and Project secret from the Secrets section.
The mode applies immediately. Users who click Sign In on the board are sent to your authentication page.
Your SSO redirect URL must use https://. During local development, use an HTTPS tunnel if you need to test the full redirect flow.
Board login modes require the Professional plan. On other plans, the setting is visible but locked.
Board login modes
The Board login setting controls how login works on your public board:
Default uses the standard UserJot login flow.
SSO redirects users to your authentication page.
Disabled hides the login button entirely.
How the flow works
When a user starts SSO, UserJot redirects their browser to your configured SSO redirect URL. The only query parameter your endpoint needs is state:
https://yourapp.com/userjot/sso?state=OPAQUE_STATE_FROM_USERJOTYour endpoint should:
Read the
statequery parameter.Authenticate the user in your app.
Create a JWT signed with your UserJot Project secret.
Redirect to
https://api.userjot.com/auth/ssowithjwtand the originalstate.
Do not modify the state value. UserJot uses it to verify that the login was started by the same browser session. The state value is one-time use and expires after 15 minutes.
If the user is not signed in yet, send them through your normal login flow and then return them to this same SSO endpoint with the original query parameters.
JWT payload
The JWT must be signed with HS256 using your UserJot Project secret.
At minimum, send the user ID, issuer, audience, and expiration. You can also include an email address, profile fields, custom traits, and company memberships.
Claim | Required | Description |
|---|---|---|
| Yes | Stable user ID from your system |
| No | User's email address |
| Yes | Exact UserJot Project ID |
| Yes | Exact string |
| Yes | Unix expiration timestamp |
| No | Unix issued-at timestamp |
| No | Given profile field |
| No | Family profile field |
| No | Avatar URL |
| No | Custom user traits |
| No | Company or account memberships |
Example payload:
const now = Math.floor(Date.now() / 1000);
const payload = {
sub: 'user_123',
email: 'jane@example.com',
firstName: 'Jane',
lastName: 'Doe',
iss: 'YOUR_PROJECT_ID',
aud: 'userjot',
iat: now,
exp: now + 10 * 60
};Use a short expiration window. Five to fifteen minutes is a good default. If you include iat, it must not be in the future beyond normal clock skew.
If email is omitted, UserJot identifies the user by sub and creates or updates a member without an email address. Email-based linking and email notifications only become available after an email address is provided.
Trait values must be JSON scalars: strings, numbers, booleans, or null. Nested objects and arrays are ignored.
Example endpoint
Hono
import { Hono } from 'hono';
import jwt from 'jsonwebtoken';
const app = new Hono();
const USERJOT_PROJECT_ID = 'YOUR_PROJECT_ID';
const USERJOT_PROJECT_SECRET = 'YOUR_PROJECT_SECRET';
const USERJOT_CALLBACK_URL = 'https://api.userjot.com/auth/sso';
app.get('/userjot/sso', async (c) => {
const state = c.req.query('state');
if (!state) {
return c.text('Missing state', 400);
}
// Authenticate the user however your app normally does.
const user = await getCurrentUser(c);
const payload = {
sub: user.id,
email: user.email,
firstName: user.firstName,
lastName: user.lastName,
iss: USERJOT_PROJECT_ID,
aud: 'userjot'
};
const token = jwt.sign(payload, USERJOT_PROJECT_SECRET, {
algorithm: 'HS256',
expiresIn: '10m'
});
const callback = new URL(USERJOT_CALLBACK_URL);
callback.searchParams.set('jwt', token);
callback.searchParams.set('state', state);
return c.redirect(callback.toString());
});Keep the Project secret on your server. Never expose it in browser code.
Company data
If your product is account-based, you can include company memberships in the same JWT payload:
{
"companies": [
{
"id": "company_456",
"domain": "acme.com",
"logo": "https://example.com/acme.png",
"traits": {
"plan": "enterprise",
"seats": 42
}
}
]
}Use id for the stable account ID from your system. You can skip company data unless it is useful for your workspace.
Troubleshooting
Users are sent back without being logged in
Check that:
the JWT is signed with the current Project secret
issmatches the exact Project ID from your workspace settingsaudis the exact stringuserjotsubandexpare presentyour endpoint sends back the exact
statevalue it received
If SSO fails after the callback, UserJot redirects back to the board with an sso_error query parameter such as invalid_state, invalid_token, auth_failed, or session_error.
The login button does not use SSO
Check that Board login is set to SSO in Settings → Login.
You changed the Project secret
After rotating the Project secret, update your SSO endpoint immediately. Tokens signed with the old secret will no longer work.