Case StudyGP.Family2026

Case study

GP.Family

Portal access by domain membership — proven live on SpiceDB and Ory Keto.

GP.Family is a platform built on Frauthy (Level 2). Its premise: a family or group brings its own identity domain, and membership grants portal access automatically. Owning foo@mypeople.com is enough.

§01

The schema

Frauthy Script

The Frauthy Schema that GP.Family runs — three entities, one computed permission, zero user lists:

// gpfamily — portal access by domain membership
entity user {}

entity domain {
  relation member: user
}

entity portal {
  relation trusted: domain
  permit access = trusted.member
}

Portal access is computed from membership of any trusted domain — pure ReBAC composition. No user lists, no invites. The schema compiles to both SpiceDB schema and Ory Keto OPL.

compiled → spicedb schema
definition user {}

definition domain {
    relation member: user
}

definition portal {
    relation trusted_domain: domain
    permission access = trusted_domain->member
}
compiled → ory permission language (keto)
class User implements Namespace {}

class Domain implements Namespace {
  related: { member: User[] }
}

class Portal implements Namespace {
  related: { trusted_domain: Domain[] }
  permits = {
    access: (ctx: Context): boolean =>
      this.related.trusted_domain.traverse(
        (d) => d.related.member.includes(ctx.subject)
      ),
  }
}
One schema, two backends. The source is identical; the compiler targets whichever Zanzibar store fits your stack. Switch with backend keto in the schema file.
§02

The login

OIDC · Identity

Foo signs in through their domain’s OIDC provider. The ID token carries a verified email claim — the only artifact the authorization side needs.

oidc login · foo@mypeople.com
{
  "iss": "https://login.mypeople.com",
  "sub": "u_8a1f…",
  "aud": "gpfamily-portal",
  "email": "foo@mypeople.com",
  "email_verified": true
}

The user authenticates through their domain’s OIDC provider. The ID token carries a verified email claim — the identity half of the story. Everything else — mapping that email to a relationship, checking portal access — happens on the authorization side, without ever touching the login flow again.

§03

The mapping

Claim → Relationship

The join that Frauthy automates — turning an identity claim into an authorization relationship.

mapping rule · claim-to-relationship
// frauthy mapping config
mapping: [{
  claimField: "email_domain",
  match: { kind: "domain_equals", value: "mypeople.com" },
  emit: {
    object: "domain:{domain}",
    relation: "member",
    subject: "user:{sub}",
  },
}]

When the email claim matches a trusted domain, Frauthy writes the relationship automatically. The resulting tuple:

domain:mypeople.com#member@user:foo@mypeople.com
Security guard. email_verified must be true — the SDK silently skips mapping rules when it is false. This private-beta security behavior prevents an unverified email from creating a relationship.
§04

The Check

Authorization · ReBAC

One call resolves portal access — no per-user lookup, no role table, just a graph walk.

the check call
Check( portal:gpfamily, access, user:foo@mypeople.com )

The store — SpiceDB or Keto, identical result — walks the graph:

  1. 01subject

    Start with the user

    Resolve the verified GP.Family member.

    user:foo@mypeople.com
  2. 02member

    Follow domain membership

    The user is a member of the registered identity domain.

    domain:mypeople.com#member
  3. 03trusted

    Follow portal trust

    The portal trusts the member’s domain.

    portal:gpfamily#trusted
  4. 04permission

    Allow portal access

    The computed access permission resolves through the complete graph.

    access → ALLOW
what the app actually writes · three lines
// the integrating app — no auth code of its own
const session = await frauthy.session(req)
if (await session.can("access", portal)) {
  return render("portal")
}
access · allow ✓

Foo reaches the portal the moment they prove they hold a @mypeople.com address — because the group owns the domain, not because anyone added Foo to a list.

Interactive lifecycle lab

Prove the seam yourself.

Change the verified identity claim, then watch Frauthy map it into the relationship graph and resolve the same access check.

MemStore · browser-only
  1. 01
    Verify OIDC identitywaiting
  2. 02
    Map claim to relationshipwaiting
  3. 03
    Check permissionwaiting
  4. 04
    Return decisionwaiting
§05

The full lifecycle

End to end

The complete request lifecycle — from OIDC token to portal render — in five stages.

  1. 01authn

    Verify

    Verify the OIDC token and extract sub plus email.

  2. 02map

    Relate

    An email_domain match writes domain membership.

    domain:mypeople.com#member@user:foo
  3. 03authz

    Check

    Check portal access for the verified user.

    portal:gpfamily#access
  4. 04decide

    Allow

    The graph walk resolves through the trusted domain.

    ALLOW
  5. 05observe

    Explain

    One OTLP trace spans authentication, mapping, authorization, and decision.

Proven live. The full OIDC → can() → DuckDB-APM observe loop runs end-to-end on real SpiceDB and Ory Keto instances.

Frauthy

GP.Family — a live-demo walkthrough of Frauthy Level 2. Portal access by domain membership, no invites, no user lists.

Platform GP.Family
Level 2 · Managed schema + claim mapping
Backends SpiceDB · Ory Keto
License Proprietary
violet = authentication / identity  ·  coral = authorization / permission  ·  Fraunces · IBM Plex Sans · IBM Plex Mono