Loyalty SSO

OIDC · v1

GuestMaker is a standard OpenID Connect Identity Provider for loyalty guests. Your website integrates as a confidential Relying Party using the Authorization Code flow with PKCE. If your platform already supports “Log in with Google / Apple / any OIDC provider,” this is the same integration pointed at our endpoints.

Booking engine quickstart

Prefer code first? A copy-paste, framework-neutral walkthrough of the whole flow.

Protocol
OAuth 2.1 + OpenID Connect
Flow
Authorization Code + PKCE (S256)
Tokens
RS256 JWT · access ~5 min
Member data
id_token claims + /api/loyalty/me/*

The flow

Authorization Code + PKCE (S256), confidential client. The guest authenticates once against our branded login; you exchange the code server-side and read member data with the access token.

Access tokens are short-lived (~5 minutes). Re-run the flow when one expires — because the guest’s session persists across all of the group’s properties, the re-login is usually silent (true SSO).

How it fits together
One issuer; the tenant (hotel group) is derived from your client_id.

We issue you a client_id and client_secret (shown once) per environment. Guests authenticate against a hotel-branded login we host; you receive an authorization code, exchange it server-side for tokens, and read member data with the access token.

Everything is discoverable at the well-known URL — always load endpoints and signing keys from discovery rather than hard-coding them, because we rotate keys.

Scopes & claims (v1)
Profile, email and tier ride inside the id_token; live balance & history come from the resource endpoints.
ScopeReturnsWhere
openidsub — the member's stable identifier. Required on every request.id_token
profilename, given_name, family_name, locale, country, updated_atid_token
emailemail (email_verified is only emitted when we have proof of ownership — never assume it for pre-existing members)id_token
loyalty:readloyalty_tier, loyalty_tier_name, points_balance, member_numberid_token + GET /api/loyalty/me/balance
transactions:readpoints earn / redemption historyGET /api/loyalty/me/transactions

Cash-credit balance is not exposed in v1.

Endpoints
Canonical paths under the issuer /api/oidc — but resolve them from discovery, not this list.
GET /api/oidc/.well-known/openid-configurationDiscovery — every endpoint, scope, and the JWKS URI. Load from here; don't hard-code.
GET /api/oidc/authAuthorization endpoint (browser redirect).
POST /api/oidc/tokenToken endpoint (server-to-server, client_secret_basic).
GET /api/oidc/jwksPublic signing keys (RS256). Cache, but refresh on an unknown kid.
GET /api/loyalty/me/balanceMember tier + points balance + credit value (Bearer access token).
GET /api/loyalty/me/transactionsMember points / redemption history (Bearer access token).

There is no UserInfo endpoint by design: profile and email arrive as id_token claims, and live member data is served by /api/loyalty/me/*. The access token is an RS256 JWT whose aud is our resource indicator — those endpoints verify it and isolate by member + tenant.

Security requirements (mandatory)
We register and enforce these; integrations that skip them break.
  • PKCE with S256 (never plain); a fresh code_verifier per attempt.
  • Send and verify state (CSRF) and nonce (replay); verify the iss response parameter equals our issuer.
  • Verify the id_token signature against our JWKS with RS256 — check iss, aud = your client_id, nonce, expiry. Never accept an unverified token; reject alg: none / HS256.
  • redirect_uri is exact-match — HTTPS only, byte-for-byte, no wildcards, no trailing-slash or case variants. Register every value you will use.
  • Keep client_secret server-side; run the token exchange from your backend only. TLS everywhere.
  • Cache the JWKS but refresh on an unknown kid — we rotate keys.
Tokens & revocation
Stateless JWTs with a real revocation backstop.

Both access_token and id_token are asymmetric RS256 JWTs you verify against the published JWKS. Access tokens last ~5 minutes.

Introspection is intentionally disabled. Don’t rely on it — verify the JWT signature and call /api/loyalty/me/*, which enforce a server-side revocation epoch (logout, ban, data-erasure and consent withdrawal invalidate outstanding access tokens immediately, even before they expire).

Error responses
Standard OAuth 2.0 errors at the token endpoint; an IdP error page (never a redirect) for a bad client_id/redirect_uri or when SSO is not enabled for the tenant.
errorMeaning
invalid_requestMissing/malformed parameter (e.g. no PKCE challenge, bad scope syntax).
invalid_clientUnknown client_id or wrong client_secret. Returned generically for both.
invalid_grantCode expired/replayed, redirect_uri mismatch, or code_verifier failed PKCE.
invalid_scopeRequested a scope not granted to your client.
access_deniedThe guest abandoned or declined the login.