Sign members in with your own login

Your server vouches for the member, Dium checks the token, and the member lands in the Flow already signed in.

Last updated

Which sign-in path to use

Dium has one recommended path for partners and two older ones. All three end with the same Dium session.

PathUse it whenStatus
One-time token (issue_token then autologin.php)You already sign members in on your site and want them in Dium without a second login.Recommended
Werify trampoline (/home/sso_login.php)Your members already use Werify, the sign-in service Dium uses.Supported
Direct URL (/home/sponsor-auth.php)Older sponsor sign-up flows that also pre-fill a sponsor page.Legacy
The direct URL path puts your partner key in a browser URL, where it can end up in history and server logs. Do not use it for new work. Use the one-time token path instead.

The token flow, step by step

Each step names who acts. The shaded row marks where trust is decided.

  1. Browser

    The member signs in on your site, the way they always do. Dium never sees their password.

  2. Your server

    Calls POST /api/auth.php with action: "issue_token", your key in the X-Api-Key header, and the member's email, name and avatar.

  3. Dium API

    Checks your key in constant time and checks its scope. Returns {"success": true, "token": "<48 hex>", "expires_in": 300}.

Trust boundary. This is where trust is decided. Only a request carrying your secret key can mint a token, so only your server can vouch for a member.

  1. Your server

    Redirects the browser to https://dium.io/home/autologin.php?token=...&redirect=/asq/d/{slug}/waves.

  2. Dium

    Checks the token format, reads the token and deletes it (one use only), then checks it has not expired.

  3. Dium

    Finds the member by Moat public key, then by email. Creates the account if it is new.

  4. Dium

    Sets HttpOnly session cookies and sends the browser to the redirect path. If that path is on your host, Dium adds ?dium_token= so your mount can sign the member in.

  5. Your mount

    Stores the token in localStorage, removes it from the address bar and calls auth.php validate with an Authorization: Bearer header.

Server-side verification is the trust boundary

The browser never proves who a member is. Your server does, by holding the key. Keep it that way:

  • Call issue_token only from your server, only after your own login check passes, and only for the member in that session. Never take the email from a query string or form field.
  • Send the key in the X-Api-Key header. The api_key body field still works but is legacy, and bodies are more likely to be logged.
  • Keep the key in an environment variable or secret manager. Never ship it in JavaScript, a mobile app or a public repo.
  • Ask for the smallest scope you need. A sign-in only integration needs auth.issue_token, not *. A key used without the right scope gets 403.

Token handling

  • Short life. A token lasts 300 seconds (expires_in). Redirect right away. Do not mint tokens ahead of time.
  • One use. Dium deletes the token when it is read. A second visit with the same link fails, so a leaked link is worth little.
  • Format check. Tokens are 48 hex characters. Reject anything else before you redirect.
  • No logging. Do not write tokens to logs or analytics. Send Cache-Control: no-store on the redirect response.
  • Mount token. On a cross-host redirect, the dium_token your mount receives is a Bearer credential for that member. The mount template removes it from the address bar at once. Do not copy it anywhere else.

Redirects and the open-redirect guard

Dium only redirects to hosts on its allowlist, ALLOWED_REDIRECT_HOSTS. If your mount lives on your own domain, we add your host during onboarding. A redirect to any other host is refused.

Do the same on your side. Pick the landing page from a fixed list, as the sample code below does. Never pass a return_to, next or event_url value from the request straight into a Location header.

Cookie notes

Dium sets these cookies on .dium.io (the SOURCE_DOMAIN). All are Secure; SameSite=Lax and last 30 days.

CookieHttpOnlyPurpose
sasq_public_keyYesCanonical identity: the member's Moat public key
emailYesFallback identifier. Refused if the account has no public key
user_keyYesWerify token
moat_pkYesAlias of the public key
sasq_logged_inNoA flag the page's JavaScript can read
sasq_display_nameNoDisplay name the page's JavaScript can read

Why Dium does not run in an iframe

SameSite=Lax cookies are not sent inside a cross-site iframe, and Dium's app pages also send X-Frame-Options: DENY. So a framed Dium app cannot keep a session. This is on purpose: it blocks clickjacking.

A framed login would need cookies set as SameSite=None; Secure; Partitioned. Dium does not set cookies that way today. Use the mount instead: it runs under your own domain, so the session works as first-party.

Sample server code

Both samples do the same four things: check your own session, mint a token with the key in a header, validate the answer, and redirect to a fixed path.

PHP, any framework
<?php
// Runs on YOUR server, after your own login check has passed.
// Never call this from the browser: the key must stay server-side.
$apiKey = getenv('DIUM_API_KEY');
$user   = my_current_user();          // your session, not a query param

$ch = curl_init('https://dium.io/api/auth.php');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => 10,
    CURLOPT_HTTPHEADER     => ['Content-Type: application/json', 'X-Api-Key: ' . $apiKey],
    CURLOPT_POSTFIELDS     => json_encode([
        'action' => 'issue_token',
        'email'  => $user['email'],
        'name'   => $user['name'],
        'avatar' => $user['avatar_url'],
    ]),
]);
$body   = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

$res = json_decode($body ?: '', true);
if ($status !== 200 || empty($res['success'])
    || !preg_match('/^[0-9a-f]{48}$/', $res['token'] ?? '')) {
    error_log('Dium issue_token failed with HTTP ' . $status);   // do not log the token
    http_response_code(502);
    exit('We could not open the community. Please try again.');
}

// Pick the landing page from a fixed list. Never forward a user-supplied URL.
$targets = ['waves' => '/asq/d/acme-summit/waves', 'pages' => '/asq/d/acme-summit/pages'];
$target  = $targets[$_GET['to'] ?? 'waves'] ?? $targets['waves'];

header('Cache-Control: no-store');
header('Location: https://dium.io/home/autologin.php?token=' . $res['token']
     . '&redirect=' . rawurlencode($target), true, 302);
exit;

Identity: Moat is the source of truth

Every Dium member has a Moat public key, a 32 character hex ID created by Moat (moat.page), the identity service shared across TAO.ai products. Dium looks members up by public key first and by email second.

  • Name and avatar come from the member's Moat profile. The name and avatar you pass to issue_token are used when the account is new.
  • Profile edits made in Moat reach Dium through a server callback at /user/update.php, which only Moat can call.
  • Email changes sent through that callback are logged but not applied, to stop account takeover.
  • Werify (werify.ai) handles Dium's own sign-in screen with an email code or social login.

Werify trampoline

If your members already sign in with Werify, you can send them to /home/sso_login.php with email, user_key, k and a redirect path. Dium checks the host against its redirect allowlist, then checks the session with Werify on the server before it sets cookies. If the check fails on the same host, the member lands on the normal sign-in page.