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

Site sign-in

How visitors sign in on your public site, with a Median account, your own app, or an OpenID Connect provider.

Signing in lets visitors post, vote, comment and follow on the feedback board, and keeps their support chat across devices. The help center never asks.

Method Visitors sign in with Setup Same customer as the widget
Median accounts A free Median account None No. Matched by email address
Your app (recommended) Their account in your product One route in your app Yes
OpenID Connect Your identity provider, like Auth0 or Okta The provider’s details When your widget signs the same sub

Pick one under Site → Sign-in. Admins and owners only. Switching signs out everyone who signed in another way.

Median accounts

The default. There is nothing to set up.

Fact Detail
What visitors see Sign in to Acme on median.sh, with Continue and Cancel
No account yet They make one on the same page. It is free
What you get Their name, email and picture
Chat history Joins whatever that email address sent you before, like email to your support address

Your app

Visitors sign in with the account they already have in your product. Their site chat and their widget chat are one history.

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.

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

Create an app at your provider

A regular web application using the authorization code flow. Add the Redirect URI from Site → Sign-in to its allowed callback URLs: https://api.median.sh/sites/oidc/callback. Allow the openid, email and profile scopes.

Add its details

Pick OpenID Connect. Enter the issuer URL, client ID and client secret, and press Use OpenID Connect.

Provider Issuer URL
Auth0 https://<tenant>.<region>.auth0.com
Okta https://<org>.okta.com, or a custom authorization server’s URL
Clerk Your Frontend API URL. Turn on the openid scope on the OAuth application
Google https://accounts.google.com
Microsoft Entra ID https://login.microsoftonline.com/<tenant-id>/v2.0
Fact Detail
Discovery Read from <issuer>/.well-known/openid-configuration when you save. Its issuer must match what you entered
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
Client secret Never shown again. The dashboard shows its last 4 characters. Leave it blank to keep it
New client Enter the secret again when the issuer or client ID changes

What signing in does

Where Signed in Signed out
Header Their picture or initial, with Sign out Sign in, when Support or Feedback is on
Feedback Post, vote, comment and follow. A vote, follow or comment pressed before signing in goes through after, and a post reopens with its draft. Images have to be attached again Read only
Support The chat follows them across devices, with no email prompt. With your app or OpenID Connect it is the widget’s history too, and your tools know who they are The chat belongs to this browser. With your app or OpenID Connect, the agent can put a Sign in button under a reply when one of your tools needs to know who they are
Help center No change No change
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.

Troubleshooting

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 or the provider sent no email Return email. For OpenID Connect, allow the email scope
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
Sign-in cancelled They pressed Cancel, or denied access at the provider Nothing
Sign-in isn’t working Your OpenID provider refused the sign-in, like a scope its app doesn’t allow or a client secret it doesn’t accept Site → Sign-in shows the provider’s reason, and Logs has it under Site sign-in. Fix it at the provider, then sign in again. The reason clears once a sign-in works
Median: site sign-in needs the person's email. Return { id, email } from your resolver. The resolver returned no email Return email
This sign-in link has expired or isn't valid. An old link, or one used twice Start again from the site
The dashboard says Fix
Enter your sign-in route's https URL. Use the full URL. http only works for localhost
We couldn't read that provider's OpenID configuration. Check the issuer URL. Open <issuer>/.well-known/openid-configuration in a browser. It has to load
That provider calls itself <issuer>. Use that as the issuer. Copy the issuer from the message
Enter the client secret from your provider. The secret is needed the first time, and after changing the client

Was this page helpful?