Skip to content

Sign users in

The guest API, and the Flutter SDK, need to know which of your users is asking. Your backend proves it with a short token, signed with your app’s identity secret. Nothing has to be published and PurrCRM fetches nothing from you.

On Settings › Connect › Sign-in, press Make a secret. It is shown once (crm_is_…). Keep it in your backend’s secrets, never in an app or a web page. A test app has its own secrets (crm_is_test_…), for your staging backend.

The token is a JWT signed with HS256:

ClaimValue
subThe user’s id: your external id for them, as in Identify.
iatWhen you signed it.
expWhen it expires: minutes or hours away. More than 30 days is refused.
iss, aud, kndOnly if your Sign-in tab names an issuer, an audience or a kind.

Each example signs a token for the user user-123, reading the secret from an environment variable of your own, CRM_IDENTITY_SECRET, and prints it.

crm_token.mjs
// npm install jsonwebtoken
import jwt from "jsonwebtoken";
const userId = "user-123";
const token = jwt.sign({ sub: userId }, process.env.CRM_IDENTITY_SECRET, {
algorithm: "HS256",
expiresIn: "1h",
});
console.log(token);

To try a token, paste it into Test a token on the Sign-in tab (below).

Give your signed-in user their token from a route of your own, behind your own sign-in. With Express, where requireSignedIn is your own sign-in’s check and req.user the user it found:

server.mjs
import express from "express";
import jwt from "jsonwebtoken";
const app = express();
app.get("/crm-token", requireSignedIn, (req, res) => {
const token = jwt.sign({ sub: String(req.user.id) }, process.env.CRM_IDENTITY_SECRET, {
algorithm: "HS256",
expiresIn: "1h",
});
res.set("Cache-Control", "no-store").json({ token });
});

Your web app sends it as Authorization: Bearer <token> (From a website or web app). The Flutter SDK fetches it with CrmTokens.fetched (Sign users in).

Paste a token into Test a token on the Sign-in tab. It says which contact the token signs in, or exactly why it is refused.

When PurrCRM refuses a token:

  • 401 with code token_expired: the signature or the time is wrong. Get a new token and try once more.
  • 401 with code unauthorized: the token can never work as it is (signed for the other mode, too long, no sub). The detail says why; asking again will not help.

Two secrets work at once. Make a new one, deploy your backend with it, wait until the old one’s Last used stops moving, then Revoke it. Revoking takes effect within 30 seconds.

  • Your own sign-in’s key set: if your sign-in already issues tokens and publishes its keys at a public https address (JWKS, with EdDSA, RS256 or ES256), name it on the Sign-in tab instead. Your tokens are then checked against it.
  • Unverified sign-in: for a prototype only, the user’s id is sent with no token, as X-CRM-User-Id. Anyone who knows an id can act as that user.