Skip to content
Median
Esc
↑↓navigate↵open⌘Jpreview
On this page

Site sign-in

Sign visitors in to your public site with their account in your product, from one route, or by signing the token yourself.

Your public site can sign visitors in with the account they already have in your product. Their site chat and their widget chat become one history, and your tools know who they are. This page is the developer half: the route, the token, and the OpenID Connect details.

To pick a method and set it up in the dashboard, see Choose how visitors sign in in the help center.

Your app

import { currentUser } from "@clerk/nextjs/server";
import { medianIdentity } from "@mediansh/agent-tools";

export const { GET } = medianIdentity(
  async () => {
    const user = await currentUser();
    if (!user) return null;
    return {
      id: user.id,
      email: user.primaryEmailAddress?.emailAddress,
      name: user.fullName ?? undefined,
      avatarUrl: user.imageUrl,
    };
  },
  {
    signIn: (returnTo) =>
      `/sign-in?redirect_url=${encodeURIComponent(returnTo)}`,
  },
);

This is the route the widget’s identity prop reads. If you have one, add email and signIn to it.

Add the route

Deploy it with MEDIAN_KEY set on the server. See Identity.

Paste its URL

Under Site → Sign-in, pick Your app, paste the route’s full https URL, and press Use your app.

Try it

Open your site and press Sign in.

How it works

  1. The site sends the visitor to your route with ?median_request=<id>.
  2. Signed in, the route signs a token and sends them to Median. Signed out, it sends them to signIn(returnTo), and your login brings them back to the route.
  3. Median checks the token and returns the visitor to the page they started on, signed in.

Without the query parameter, the route answers the widget as before.

Resolver

Field Required Detail
id Yes Your id for the person. The widget’s signed id for them must match
email Yes The board counts one vote per email
name No Shown in the site header and to your team
avatarUrl No https only

Options

Option Default Detail
signIn None (returnTo) => string. Your login page, given this route’s full URL to come back to. Without it, a signed out visitor goes back to the site with Sign in to Acme first
key MEDIAN_KEY The Median key to sign with
apiUrl MEDIAN_API_URL, then https://api.median.sh Median’s API origin

Other servers

medianSiteRedirect builds the redirect for a server without Web Request and Response:

import { medianSiteRedirect } from "@mediansh/agent-tools";

app.get("/median/sign-in", async (req, res) => {
  const user = req.session.user;
  if (!user) {
    return res.redirect(`/login?next=${encodeURIComponent(req.originalUrl)}`);
  }
  res.set("cache-control", "no-store");
  res.redirect(
    await medianSiteRedirect(String(req.query.median_request), {
      id: user.id,
      email: user.email,
      name: user.name,
    }),
  );
});

medianSiteRedirect(request, null) sends a signed out visitor back to the site instead.

Export Use
medianIdentity The route above. Answers the widget, and signs visitors in to the site
medianSiteRedirect(request, user, options?) Async. The redirect URL for one sign-in, on any server
signMedianSiteToken(request, user, options?) Async. The token alone, when you build the redirect yourself
medianSiteSigningKey(key) Async. The key tokens are signed with, worked out from a Median key
MEDIAN_SITE_REQUEST_PARAM "median_request", the query parameter the site sends
MedianSiteSignInOptions The options type: key and apiUrl

The token

For a language without the SDK. Redirect to https://api.median.sh/sites/sign-in?token=<jwt>, or to https://api.median.sh/sites/sign-in?request=<median_request>&error=signed_out when nobody is signed in.

Part Value
Algorithm HS256
Header { "alg": "HS256", "typ": "JWT", "kid": "<median_pk_ id>" }
Claims aud: "median-site", req, sub, email, name?, picture?, iat, exp
req The median_request value
Lifetime exp - iat of 600 seconds or less. 300 is typical
Identity secret "median_sig_" + hex(SHA-256("median:identity:" + MEDIAN_KEY))
Signing key hex(HMAC-SHA256(identity secret, "median-site-sign-in")), used as a UTF-8 string

median_pk_ is publicKeyFromMedianKey(MEDIAN_KEY). Each token works once.

OpenID Connect

Setup happens at your provider and on Site → Sign-in. The steps and the issuer URL for each provider are in the help center. What Median does with the provider:

Fact Detail
Discovery Read from <issuer>/.well-known/openid-configuration when you save. Its issuer must match what you entered
Redirect URI https://api.median.sh/sites/oidc/callback
Scopes openid email profile
Flow Authorization code with PKCE. The secret goes as client_secret_basic when the provider supports it, else client_secret_post
Person sub is their id. email is required and refused when email_verified is false. name and picture are used when present
Linked to the widget When your app signs the widget’s id with the same sub

Sessions

Fact Detail
Session 30 days, in a cookie on the site’s address
Sign out Ends the session and starts a fresh chat in that browser
Before signing in Anything the browser said in the chat joins the signed in person’s history
Addresses Works on <slug>.median.website and a live custom domain
Time limit A sign-in has 15 minutes to finish

API and CLI

Action CLI REST
Show median site sign-in get GET /v1/site/sign-in
Median accounts median site sign-in set median PATCH /v1/site/sign-in
Your app median site sign-in set app --app-url <url> PATCH /v1/site/sign-in
OpenID Connect median site sign-in set oidc --issuer <url> --client-id <id> --client-secret <secret> PATCH /v1/site/sign-in

MCP: median.site.signIn() and setSignIn(...). The client secret is never returned.

Errors from your route

The visitor sees Cause Fix
Sign in to Acme first Your route found nobody signed in, and has no signIn Add signIn to medianIdentity
No email address The resolver sent no email Return email
That didn’t work The token did not verify, the sign-in took over 15 minutes, or it was opened in another tab Check MEDIAN_KEY on the route is a current key from this organization, then try again in one tab
Median: site sign-in needs the person's email. Return { id, email } from your resolver. The resolver returned no email Return email

Problems visitors and admins see, like a provider refusing the sign-in, are in Fix public site problems.

Was this page helpful?