Architecture
Nothing exotic. Everything verifiable.
The complete technical specification of the Legal.id identity system: protocols, endpoints, token formats, claim sets, assurance semantics, and data handling. Written to be evaluated, not believed.
1 · System
One issuer, many relying parties.
Legal.id is a standalone OpenID Connect identity provider operated by Lextronica, Inc. Relying parties (first: Jane) integrate over standard OIDC — no proprietary SDK, no custom protocol. Applications never see credentials; they receive signed tokens carrying only consented claims.
| Endpoint | Path | Notes |
|---|---|---|
| Discovery | /.well-known/openid-configuration | Canonical metadata; everything below is advertised here |
| Authorization | /oauth/authorize | Browser redirect; consent screen for third-party clients |
| Token | /oauth/token | Code exchange + refresh; client secret over TLS |
| UserInfo | /oauth/userinfo | Bearer access token; same claim set as the ID token |
| JWKS | /oauth/discovery/keys | RSA public key(s), kid-tagged |
| Revocation | /oauth/revoke | RFC 7009; client-authenticated |
| End session | /logout/oidc | RP-initiated logout; validated post_logout_redirect_uri |
| DID document | /u/{handle}/did.json | did:web resolution target |
| Attestation | /u/{handle}/attestation.jwt | Fresh EdDSA-signed JWS of the identity's public facts |
| Verify (human) | /verify?u={handle} | Public lookup page; performs and displays the JWS check |
2 · Protocol
OAuth 2.0 + OIDC, held to OAuth 2.1 rules.
| Grant types | authorization_code + refresh only. No implicit, no password grant, no client credentials. |
|---|---|
| PKCE | Required (S256). Enforced server-side for public clients; confidential clients send it too (Jane does). |
| Clients | Confidential only in v1; registered by the operator (no self-serve registration). Exact redirect-URI matching; HTTPS required outside development. |
| State / nonce | Round-tripped and validated by the reference RP integration; nonce is embedded in the ID token. |
| Consent | Explicit consent screen, per scope, for third-party clients. First-party clients are pre-authorized. Consent pages send frame-ancestors 'none' (clickjacking defense, app-wide). |
| Authorization code | Single use · expires in 60 seconds |
|---|---|
| Access token | Opaque (not a JWT) · 2 hours · revocable |
| Refresh token | Rotated on every use; the replaced token is invalidated |
| ID token | JWT, RS256, kid in header · 2 hours · aud = client id, iss = https://legal.id |
| Signing key | RSA-2048. Published at JWKS. Rotation is a deploy procedure: new key issues immediately; old tokens age out within the 2-hour TTL. Multi-key JWKS publication is a scheduled follow-up. |
| Subject | Opaque stable UUID (sub), never the handle — handles can evolve without breaking RP links |
3 · Claims
Scopes map to claims. Nothing extra.
| Scope | Claims released (ID token + UserInfo) |
|---|---|
| openid | sub (opaque UUID), standard JWT claims (iss, aud, exp, iat, nonce, auth_time) |
email, email_verified (boolean — RPs are instructed not to link accounts when false) | |
| profile | name, preferred_username (the handle), legal.id/tier, legal.id/did |
4 · Assurance
The tier states what we verified. Nothing more.
Tiers are defined by the verification event that produced them and are carried in the token (legal.id/tier), so relying parties make their own trust decisions.
| Tier | Verification performed | Recorded |
|---|---|---|
| Digital | Control of the email address proven via confirmation link | email_verified_at |
| Core | Identity corroborated by sign-in with a verified Google account | google_verified_at + provider uid |
| Verified | Form I-9 employment-eligibility verification completed by Legal.io — an in-person review of government identity documents | Attestation event only: basis, attesting admin, timestamp. Documents, document numbers and SSNs are never stored or entered. |
I-9 attestation semantics
Only Legal.id administrators can attest, and only for people whose I-9 Legal.io has actually completed. The attestation sets a re-attestation date 36 months out (reattest_on, printed on the card) and writes an immutable audit event. Attestations are revocable at any time; revocation drops the account to the tier its remaining verifications support.
Unverified accounts
An account whose email is not yet confirmed is Unverified: its card renders, but tokens carry email_verified: false and relying parties are told not to link accounts on it.
5 · Decentralized identity
did:web, resolvable by anything.
Resolution
did:web:legal.id:u:{handle} ⇩ resolves tohttps://legal.id/u/{handle}/did.json ⇩ publishesEd25519 key (publicKeyMultibase, z6Mk…)profile + verification service endpointsMethod: did:web (path form). Upgrade path to did:webvh (verifiable history) as the method matures. Key custody is server-side today, with private keys encrypted at rest; holder-controlled keys arrive with the wallet seam.
Attestation (JWS)
GET /u/{handle}/attestation.jwt ⇩ compact JWS, alg=EdDSAheader: { alg, typ, kid: {did}#key-1 }payload: { iss, sub: {did}, handle, legal.id/tier, legal.id/issued, iat, exp: iat+600 } ⇩ verify againstdid.json → Ed25519 public keyTen-minute validity: it is a fresh, timestamped assertion of current status, not a bearer credential. Wallet-held verifiable credentials (OpenID4VCI/VP, selective disclosure) are the scheduled next step and are not claimed until they ship.
6 · Sessions, sign-in, logout
Session semantics.
| Sign-in methods | Passkeys (WebAuthn, discoverable credentials, counter-checked) · password (min 10 chars, bcrypt) with mandatory email verification · optional Google corroboration |
|---|---|
| Session store | Server-side records (IP + user agent), signed httponly cookie, SameSite=Lax, Secure in production |
| Lifetime | 30 days absolute · 14 days idle · users see all active sessions and can revoke any, or all others at once |
| Fixation defense | Rails session reset on every privilege transition |
| RP-initiated logout | end_session_endpoint in discovery. Ends the IdP session; redirects only to origins registered by a known client (open-redirect defense); state round-tripped. |
|---|---|
| App revocation | Users revoke any connected app from the dashboard — all its tokens and grants are invalidated immediately. Clients can revoke via RFC 7009. |
| Recovery | Email-based password reset (30-minute single-use token); completing a reset invalidates all other sessions. Generic responses on request (no account enumeration). |
| Audit | Append-only event log: sign-ins (and failures), sign-outs, token issue/refresh/revocation, consents, passkey add/remove/failed attempts, verification changes, attestations. Recent events shown to the user; write failures alarm rather than drop silently. |
7 · Abuse resistance
Rate limits.
| Surface | Limit |
|---|---|
| Password login | 10/min per IP · 6 per 5 min per email |
| Signup | 8 per 5 min per IP (invite brute-force defense; invite redemption is atomic and capped) |
| Token endpoint | 30/min per IP |
| Password reset | 5 per 15 min per IP |
| Passkey endpoints | 30/min per IP |
TLS is forced (HSTS). All responses send frame-ancestors 'none'.
8 · Data
What we hold, what RPs get.
Stored: email, handle, display name, credential material (password hash / passkey public keys / Google uid), an Ed25519 DID keypair (private key encrypted at rest), and security logs. Never stored: plaintext passwords, I-9 documents or numbers, SSNs. Relying parties receive only consented claims. Retention and rights: see the privacy policy and terms.
9 · Roadmap
Where this goes.
Live now
The identity core.
OpenID Connect provider, passkey + password sign-in, invite-gated handles, the Legal.id card, DID documents with signed attestations, public verification, session and app management, I-9 attestation, audit logging — with Jane live as the first relying party.
Next
Credentials that travel.
Wallet-held verifiable credentials over OpenID4VCI/VP — starting with a Legal.io-attested professional credential using selective disclosure. The QR on every card becomes a live presentation endpoint.
Then
Organizations and delegation.
Firm accounts, delegated authority (firm → lawyer), and Legal.io production sign-in. Enterprise federation where the market demands it.
Context
Standards weather report.
W3C Verifiable Credentials 2.0 is a Recommendation; OpenID4VP/VCI 1.0 are final; EU eIDAS 2.0 wallets are mandated by end-2026. Passkeys passed five billion in active use. Legal.id is built to meet all of it.
Identity is infrastructure.
When trust matters, bring the ID built for it.