Single sign-on

Single sign-on lets users sign in to your public UserJot board through your own authentication system. When users click Sign In, UserJot sends them to your SSO endpoint. After you authenticate them, you send them back to UserJot with a signed JWT.

This is for public board login. It is separate from automatic login, which carries widget-identified users into public UserJot surfaces from your app.

Enable SSO

  1. Open Settings in your workspace.
  2. Go to Login.
  3. Under Authentication, set Board login to SSO. The first time you do this, an Enable SSO dialog asks for your redirect URL.
  4. 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.
  5. Copy your Project ID and Project secret from the Secrets section.

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.

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_USERJOT

Your endpoint should:

  1. Read the state query parameter.
  2. Authenticate the user in your app.
  3. Create a JWT signed with your UserJot Project secret.
  4. Redirect to https://api.userjot.com/auth/sso with jwt and the original state.

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.

ClaimRequiredDescription
subYesStable user ID from your system
emailNoUser's email address
issYesExact UserJot Project ID
audYesExact string userjot
expYesUnix expiration timestamp
iatNoUnix issued-at timestamp
firstNameNoGiven profile field
lastNameNoFamily profile field
avatarNoAvatar URL
traitsNoCustom user traits
companiesNoCompany 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
  • iss matches the exact Project ID from your workspace settings
  • aud is the exact string userjot
  • sub and exp are present
  • your endpoint sends back the exact state value 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 SettingsLogin.

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.