Language

Login with Graydient™ — Developer Specification

Last updated 2026-10-30 · Package @graydient/login-with-graydient · Spec v1.0.0

Open .md

Login with Graydient™ is the shared, Clerk-based sign-in for Graydient apps. One look, one button label, one session model. Each app keeps its own Clerk keys, JWT template, audience, and allowed origins. This page is the public v1.0 contract for integrators.

What it is

Login with Graydient™ is a shared, Clerk-based sign-in library for Graydient apps. One look, one button label, one session model. Each app keeps its own Clerk keys, JWT template, audience, and allowed origins. The library does not own production secrets.

Frontend API (public): https://clerk.graydient.ai. Package repository: https://gitlab.com/miamisprings/login-with-graydient.

Supported providers (v1.0.0)

ProviderStatusNotes
Graydient (email code)OnClerk email code; available when Clerk is configured.
GoogleOnClerk OAuth — enable on the app’s Clerk instance.
DiscordOnClerk OAuth — enable on the app’s Clerk instance.
TelegramWhen availableSeparate widget flow. Show only when Telegram login is configured and healthy; otherwise hide it.
FacebookComing soonLeave disabled until keys exist.
AppleComing soonLeave disabled until keys exist.

Clerk’s prebuilt SignIn UI shows whichever social providers the instance has enabled. Telegram is not a Clerk social connection.

Official “Login with Graydient” button

Integrators MUST use the exact official button with the Graydient logo mark and the label Login with Graydient. Do not recolor, restyle, rename, or invent a third variant. Only these two official variants are allowed.

Black on white

Login with Graydient
<!-- Official: black on white -->
<a class="lwg-btn lwg-btn--bow" href="{SIGN_IN_URL}" aria-label="Login with Graydient">
  <img class="lwg-btn__mark" src="https://graydient.ai/developers/login-with-graydient/graydient-mark-black.svg" width="18" height="19" alt="" decoding="async">
  <span class="lwg-btn__label">Login with Graydient</span>
</a>

White on black

Login with Graydient
<!-- Official: white on black -->
<a class="lwg-btn lwg-btn--wob" href="{SIGN_IN_URL}" aria-label="Login with Graydient">
  <img class="lwg-btn__mark" src="https://graydient.ai/developers/login-with-graydient/graydient-mark-white.svg" width="18" height="19" alt="" decoding="async">
  <span class="lwg-btn__label">Login with Graydient</span>
</a>

Shared CSS

.lwg-btn {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  min-height: 44px;
  padding: 10px 18px 10px 14px;
  border-radius: 8px;
  font: 600 15px/1.2 Inter, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  text-decoration: none;
  box-sizing: border-box;
  white-space: nowrap;
}
.lwg-btn__mark { display: block; width: 18px; height: 19px; flex: 0 0 auto; }
.lwg-btn__label { letter-spacing: -0.01em; }
.lwg-btn--bow {
  color: #000;
  background: #fff;
  border: 1px solid #000;
}
.lwg-btn--bow:hover { background: #f5f5f5; color: #000; }
.lwg-btn--wob {
  color: #fff;
  background: #000;
  border: 1px solid #000;
}
.lwg-btn--wob:hover { background: #111; color: #fff; }
/* Clear space: leave at least the mark height (19px) of empty space around the button. */
/* Minimum size: do not scale below 44px tall or shrink the mark below 16px. */

Do

  • Use the exact label Login with Graydient.
  • Use the official mark (G mark only — no wordmark).
  • Pick black-on-white or white-on-black.
  • Leave clear space ≥ mark height (~19px) around the button.
  • Keep minimum height 44px; mark at least 16px tall.

Don’t

  • Don’t recolor the mark or invent brand colors.
  • Don’t change the label (“Sign in with Graydient”, “Continue with Graydient”, etc.).
  • Don’t stretch, skew, or add effects that alter the mark.
  • Don’t place the button on a busy photo without a solid backing plate.
  • Don’t ship a custom icon in place of the Graydient mark.

Quickstart

  1. Install the package pinned to v1.0.0 (private GitLab).
  2. Configure Clerk for your app: JWT template (RS256, lifetime ≤ 60s) with aud, email, and email_verified (must be true).
  3. Pass a config object into validateConfig (publishable key, frontend API, template name, audience, issuer, allowed return prefixes, production origin).
  4. Protect routes with Authorization: Bearer <template-jwt> — never the Clerk session cookie alone for privileged pages.
  5. Redirect safely with validateReturnTo; on sign-out call Clerk.signOut() then a safe return_to.
  6. Render the shared sign-in page (heading always Login with Graydient) and the official button on any entry point that starts sign-in.
npm install git+https://gitlab.com/miamisprings/login-with-graydient.git#v1.0.0

Config shape

import { validateConfig, type LoginWithGraydientConfig } from '@graydient/login-with-graydient';

const config: LoginWithGraydientConfig = {
  clerkPublishableKey: env.CLERK_PUBLISHABLE_KEY,
  clerkFrontendApi: 'https://clerk.graydient.ai',
  jwtTemplateName: 'clerk-<app>-template',
  jwtAudience: '<app-aud>',
  jwtIssuer: 'https://clerk.graydient.ai',
  jwtAzp: 'https://<app-host>',           // checked when claim present
  signInPath: '/signin',
  allowedReturnToPrefixes: ['/portal', '/billing/', '/signin'],
  defaultReturnTo: '/portal',
  productionOrigin: 'https://<app-host>',
  maxTokenLifetimeSeconds: 60,
  clockSkewSeconds: 5,
};
validateConfig(config);

Replace placeholders with your app’s real values. Do not commit secrets. The library does not own production keys.

return_to

After sign-in, redirect only to same-origin relative paths allowed by your app’s prefix list. Use validateReturnTo from the package. Reject absolute URLs, //…, javascript:, path traversal, and control characters. When missing or invalid, use your defaultReturnTo.

Allowed origins

  • Production: exactly your app’s public HTTPS origin.
  • Localhost: only in non-production, and only when your app explicitly enables it.
  • Use the allow-list for CORS, CSRF Origin checks, and Clerk azp when present.

Session handling

  1. Load the pinned Clerk JS version from the package — do not use @latest.
  2. On protected HTML without Authorization, serve a bootstrap page (no customer data) that obtains the template JWT and re-fetches with the Bearer token.
  3. Verify JWT via issuer JWKS: iss, aud, optional azp, email + email_verified === true, lifetime ≤ 60s.
  4. JWKS unreachable → 503. Bad or missing token → 401 / redirect to sign-in.
  5. If a session exists but the template token fails, show “sign out and try again” — do not bounce forever.

Sign-out

  • Client: await Clerk.signOut(), then send the user to your sign-in path with a safe return_to.
  • Clear any app session cookies your app set; the library does not set cookies by itself.
  • After sign-out, never leave a usable template JWT in memory for privileged fetches.

Telegram

Telegram is optional and separate from Clerk social connections. Show the widget only when available — configured and healthy. Otherwise hide it. Verify auth server-side. Do not auto-link Clerk users by email from Telegram; bind by a server-controlled Telegram id. Billing still requires email_verified === true.

Versioning

Semver. This release is 1.0.0. Breaking changes to config shape, JWT claim rules, branding string, or provider show/hide semantics require a major bump and SPEC revision. Tags look like v1.0.0, v1.1.0, …

Need to know

  • Required button: exact label + official mark; black-on-white or white-on-black only.
  • Each app owns its Clerk keys, JWT template, audience, and allowed origins.
  • Validate return_to; lock down origins; Bearer template JWT for privileged routes.
  • Telegram only when available; Facebook/Apple coming — leave disabled.
  • Use “Copy for LLM agent” (or the .md) when handing this to an coding agent.