OpenGate/ Docs

Data Model

Each service owns its own PostgreSQL schema. No cross-service foreign keys.

On this page


Schema Overview

┌──────────────────────────────────────────────────────────┐
│                    opengate_realms                       │
│  ┌──────────────────────────────────────────────────┐    │
│  │  realms (id · name · smtp_settings · policy...)  │    │
│  └──────────────────────────────────────────────────┘    │
└──────────────────────────────────────────────────────────┘
                            ▼ realm_name (logical FK)
┌──────────────────────────────────────────────────────────┐
│                    opengate_users                        │
│  ┌────────────────────────────────────────────────┐      │
│  │  users (id · realm_name · username · email...) │      │
│  └────────────────────────────────────────────────┘      │
│  ┌────────────────────────────────────────────────┐      │
│  │  user_credentials (id · user_id · secret_hash) │      │
│  └────────────────────────────────────────────────┘      │
└──────────────────────────────────────────────────────────┘
                            ▼
┌──────────────────────────────────────────────────────────┐
│                    opengate_rbac                         │
│  ┌────────────────────────────────────┐                  │
│  │  roles (id · realm_name · name...) │                  │
│  └────────────────────────────────────┘                  │
│  ┌──────────────────────────────────────────────────┐    │
│  │  user_role_mappings (id · user_id · role_id...)  │    │
│  └──────────────────────────────────────────────────┘    │
└──────────────────────────────────────────────────────────┘
                            ▼
┌──────────────────────────────────────────────────────────┐
│                    opengate_clients                      │
│  ┌──────────────────────────────────────────────────┐    │
│  │  oauth_clients (id · client_id · redirect_uris..)│    │
│  └──────────────────────────────────────────────────┘    │
└──────────────────────────────────────────────────────────┘

Realm

V1__create_realms.sqlsql
CREATE TABLE realms (
  id                             UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  name                           VARCHAR(100) UNIQUE NOT NULL,  -- slug: ^[a-z0-9-]+$
  display_name                   VARCHAR(255),
  login_theme                    VARCHAR(50)  DEFAULT 'default',
  token_lifespan_seconds         INT          DEFAULT 300,
  refresh_token_lifespan_seconds INT          DEFAULT 2592000,
  mfa_required                   BOOLEAN      DEFAULT false,
  password_policy                JSONB,       -- { minLength, requireUppercase, ... }
  smtp_settings                  JSONB,       -- { host, port, username, ... }
  enabled                        BOOLEAN      DEFAULT true,
  created_at                     TIMESTAMPTZ  DEFAULT NOW(),
  updated_at                     TIMESTAMPTZ  DEFAULT NOW()
);

User & Credentials

V1__create_users.sqlsql
CREATE TABLE users (
  id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  realm_name      VARCHAR(100) NOT NULL,
  username        VARCHAR(150) NOT NULL,
  email           VARCHAR(255) NOT NULL,
  first_name      VARCHAR(100),
  last_name       VARCHAR(100),
  enabled         BOOLEAN     DEFAULT true,
  email_verified  BOOLEAN     DEFAULT false,
  created_at      TIMESTAMPTZ DEFAULT NOW(),
  updated_at      TIMESTAMPTZ DEFAULT NOW(),
  UNIQUE (realm_name, username),
  UNIQUE (realm_name, email)
);

CREATE TABLE user_credentials (
  id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id         UUID        NOT NULL REFERENCES users(id) ON DELETE CASCADE,
  credential_type VARCHAR(50) NOT NULL,  -- PASSWORD | TOTP
  secret_hash     TEXT        NOT NULL,  -- BCrypt(12) for PASSWORD, base32 for TOTP
  created_at      TIMESTAMPTZ DEFAULT NOW()
);

Roles & Mappings

V1__create_rbac.sqlsql
CREATE TABLE roles (
  id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  realm_name  VARCHAR(100) NOT NULL,
  name        VARCHAR(100) NOT NULL,
  description TEXT,
  composite   BOOLEAN DEFAULT false,
  created_at  TIMESTAMPTZ DEFAULT NOW(),
  UNIQUE (realm_name, name)
);

CREATE TABLE user_role_mappings (
  id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id     UUID        NOT NULL,
  role_id     UUID        NOT NULL REFERENCES roles(id) ON DELETE CASCADE,
  realm_name  VARCHAR(100) NOT NULL,
  UNIQUE (user_id, role_id)
);

OAuth Clients

V1__create_clients.sqlsql
CREATE TABLE oauth_clients (
  id                 UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  realm_name         VARCHAR(100) NOT NULL,
  client_id          VARCHAR(100) UNIQUE NOT NULL,  -- client-{uuid8}
  client_secret_hash TEXT,                          -- BCrypt(12), null for public clients
  public_client      BOOLEAN  DEFAULT false,
  pkce_required      BOOLEAN  DEFAULT true,
  redirect_uris      TEXT[],
  web_origins        TEXT[],
  grant_types        TEXT[],  -- authorization_code | client_credentials | refresh_token
  scopes             TEXT[],  -- openid profile email
  enabled            BOOLEAN  DEFAULT true,
  created_at         TIMESTAMPTZ DEFAULT NOW()
);

Sessions (Redis)

Sessions are stored in Redis as hashes, not in PostgreSQL:

Key:   session:{realmId}:{sessionId}
Type:  Hash
TTL:   8 hours (default)

Fields:
sessionId    → uuid
userId       → uuid
realmId      → string
clientIds    → comma-separated list
ipAddress    → string
userAgent    → string
createdAt    → ISO-8601 timestamp
expiresAt    → ISO-8601 timestamp

User session index (for listing sessions per user):
Key:   user-sessions:{realmId}:{userId}
Type:  Set  (values = sessionIds)

Kafka Events

DomainEvent.javajava
public record DomainEvent(
  String eventId,              // UUID
  String eventType,            // user.created | auth.login.success | ...
  String aggregateId,          // userId, sessionId, etc.
  String realm,
  Map<String, Object> payload,
  Instant occurredAt
) {
  public static DomainEvent of(
      String type, String id, String realm, Map<String, Object> payload
  ) {
      return new DomainEvent(
          UUID.randomUUID().toString(), type, id, realm, payload, Instant.now()
      );
  }
}
Event TypeAggregateKey Payload Fields
user.createduserIdemail, realm, username
user.email_verifieduserIdrealm
auth.login.successuserIdrealm, clientId, ip
auth.login.failureusernamerealm, reason, ip
session.terminatedsessionIduserId, realm
mfa.otp_sentuserIdchannel (email/sms)

Event retention

Kafka topics default to 7-day retention. Increase retention.ms for compliance requirements.