Sign-in from your app
If the people reviewing your site are already signed in to it, they shouldn't have to sign in to Marca as well. Your app can vouch for its signed-in user, and the Marca widget signs them in on the spot — no popup.
Your server signs a short-lived token (a JWT) that says who the user is. The widget swaps it for a Marca session that can comment and create tasks on this one project, and nothing else. It never becomes a Marca dashboard login and never joins your workspace.
1. Turn it on
In your project, open Access → Sign-in from your app and pick how your app signs:
- Shared secret (HS256). Marca generates a secret and shows it once. Store it on your server only — never in browser code.
- Your public keys (JWKS URL). Your app signs with its own private key and publishes the public keys at an https URL. RS256, PS256, ES256 and EdDSA are accepted.
New people who sign in this way appear under External reviewers as pending until you approve them. Turn on Let new host users in automatically to skip that step. You can also restrict which email domains are accepted.
2. Sign a token on your server
The token must carry exactly these claims, and live for 5 minutes or less:
| Claim | Value |
|---|---|
sub | Your app's stable id for the user. Not an email. |
email | The user's email, shown in Marca. |
name | Optional display name. |
aud | Your project token, proj_…. |
iat, exp | Issued-at and expiry, in seconds. exp − iat ≤ 300. |
jti | A random id. Each token works once. |
Node (jose)
import { SignJWT } from 'jose';
import { randomUUID } from 'node:crypto';
const secret = new TextEncoder().encode(process.env.MARCA_SSO_SECRET);
export function marcaToken(user) {
return new SignJWT({ email: user.email, name: user.name })
.setProtectedHeader({ alg: 'HS256' })
.setSubject(String(user.id))
.setAudience(process.env.MARCA_PROJECT) // proj_…
.setIssuedAt()
.setExpirationTime('5m')
.setJti(randomUUID())
.sign(secret);
}
Express — an endpoint your page calls, behind your own login:
app.get('/marca-token', requireLogin, async (req, res) => {
res.set('Cache-Control', 'no-store').type('text/plain').send(await marcaToken(req.user));
});
Next.js (App Router):
// app/marca-token/route.ts
export async function GET() {
const user = await currentUser(); // your auth
if (!user) return new Response(null, { status: 401 });
return new Response(await marcaToken(user), { headers: { 'Cache-Control': 'no-store' } });
}
Rails (ruby-jwt):
def marca_token
now = Time.now.to_i
payload = { sub: current_user.id.to_s, email: current_user.email, name: current_user.name,
aud: ENV['MARCA_PROJECT'], iat: now, exp: now + 300, jti: SecureRandom.uuid }
render plain: JWT.encode(payload, ENV['MARCA_SSO_SECRET'], 'HS256')
end
3. Hand it to the widget
Single-page apps (recommended). Give the widget a function that fetches a fresh token. It's called on page load and again whenever the Marca session expires, so reviewers stay signed in without reloads. Put this before the snippet tag:
<script>
window.marca = window.marca || { q: [],
identify(p) { this.q.push(['identify', p]) },
linkAccount() { this.q.push(['linkAccount']) } };
window.marca.identify(() => fetch('/marca-token').then((r) => r.text()));
</script>
<script src="https://marca.dev/snippet/r.js" data-project="proj_…" defer></script>
Call window.marca.identify(...) again whenever the signed-in user changes. Until the new user is confirmed, the widget sends nothing as the previous one. If your provider returns an empty string because nobody is signed in, the reviewer is signed out of Marca too.
Include the identity on every page. Marca remembers a reviewer between pages, but only trusts that memory once your page confirms the same user is still signed in. A page with neither identify() nor data-user-token can't confirm anyone, so the reviewer appears signed out there. That includes your signed-out pages, which is how a host logout also ends the Marca session.
Server-rendered pages. Render a token straight into the snippet tag. It works once, so renewal after the Marca session ends needs a page reload:
<script src="https://marca.dev/snippet/r.js" data-project="proj_…" data-user-token="<%= marca_token %>" defer></script>
If anything doesn't check out, the widget simply shows its normal Sign in button. The reason appears under Recent sign-ins that fell back on the Access tab.
External reviewers
People who sign in from your app are listed under Access → External reviewers, not in your workspace members. From there you can:
- Approve someone waiting for access.
- Revoke access. Their Marca session stops working immediately.
- Invite to workspace. This sends the normal invitation email to the address your app first vouched for. Only when they accept with a Marca account for that address do they join. Their sign-in through your app stays a separate, reviewer-only identity until they link it.
A reviewer who has a Marca account under the same email can link it themselves, so their feedback from your site shows under their own name. Add a button that calls window.marca.linkAccount(); it opens a Marca window where they sign in and confirm. This needs the function form of identify. Linking needs both: being signed in to your app as that user, and being signed in to Marca with the email your app first vouched for.
Security
- Reviewer-only, one project. A token from your app can never edit tasks, export, change settings, or reach another project — even for someone who is an owner in Marca.
- Your app can't impersonate a Marca account. If your token asserts the email of an existing Marca user, Marca still creates a separate external reviewer. Linking needs that person to sign in to Marca themselves.
- Short-lived and single-use. Tokens over 5 minutes old, or reused, are refused. Marca sessions from your app last 24 hours.
- Rotate any time. Rotate issues a new secret; the old one keeps working for 10 minutes while you deploy. If a secret leaks, use Rotate now instead: the old secret stops working immediately and everyone signed in through your app is signed out.
- An identity's email is fixed. The address your app sends the first time a user signs in is the one Marca keeps. Later tokens can update the display name but not the email.
- JWKS is fetched safely. Only public https addresses, no redirects, small responses, cached for 5 minutes.