Login with Graydient™ — Developer Specification
Last updated 2026-10-30 · Package @graydient/login-with-graydient · Spec v1.0.0
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.
- Overview
- Providers
- Official button
- Quickstart
- return_to
- Origins
- Session
- Sign-out
- Telegram
- Versioning
- Need to know
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)
| Provider | Status | Notes |
|---|---|---|
| Graydient (email code) | On | Clerk email code; available when Clerk is configured. |
| On | Clerk OAuth — enable on the app’s Clerk instance. | |
| Discord | On | Clerk OAuth — enable on the app’s Clerk instance. |
| Telegram | When available | Separate widget flow. Show only when Telegram login is configured and healthy; otherwise hide it. |
| Coming soon | Leave disabled until keys exist. | |
| Apple | Coming soon | Leave 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
<!-- 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
<!-- 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
- Install the package pinned to
v1.0.0(private GitLab). - Configure Clerk for your app: JWT template (RS256, lifetime ≤ 60s) with
aud,email, andemail_verified(must betrue). - Pass a config object into
validateConfig(publishable key, frontend API, template name, audience, issuer, allowed return prefixes, production origin). - Protect routes with
Authorization: Bearer <template-jwt>— never the Clerk session cookie alone for privileged pages. - Redirect safely with
validateReturnTo; on sign-out callClerk.signOut()then a safereturn_to. - 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
Originchecks, and Clerkazpwhen present.
Session handling
- Load the pinned Clerk JS version from the package — do not use
@latest. - On protected HTML without
Authorization, serve a bootstrap page (no customer data) that obtains the template JWT and re-fetches with the Bearer token. - Verify JWT via issuer JWKS:
iss,aud, optionalazp,email+email_verified === true, lifetime ≤ 60s. - JWKS unreachable → 503. Bad or missing token → 401 / redirect to sign-in.
- 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 safereturn_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.