Documentation

Sign in with Presence+

One account across every application on the Network. Your users sign in with Presence+; your application receives who they are and which organizations they belong to, as claims. The issuer is https://api.presence.plus.

Endpoints

  • Discovery: /.well-known/openid-configuration
  • Authorization: /api/oauth/authorize
  • Token: /api/oauth/token (client_secret_basic, client_secret_post, or none with PKCE for public clients)
  • Userinfo: /api/oauth/userinfo
  • Keys: /.well-known/jwks.json

Scopes

openid profile email offline_access are standard. The Network scopes are memberships:read, community:member, courses:read, pathways:walk, home:read, devotions:pray and clinical:client; request only what the application uses, and the consent screen shows the person exactly that.

Claims

sub, name, email, email_verified, picture, and memberships: the organizations the person belongs to and their role in each. Roles live on the relationship, not on the person, so the same account can be a leader in one community and a member in another.

A confidential client in a dozen lines

// Discovery
const ISSUER = "https://api.presence.plus";
const d = await fetch(`${ISSUER}/.well-known/openid-configuration`).then((r) => r.json());

// Authorization request (code + PKCE)
const url = new URL(d.authorization_endpoint);
url.searchParams.set("client_id", process.env.PRESENCE_CLIENT_ID!);
url.searchParams.set("redirect_uri", "https://yourapp.example/auth/callback");
url.searchParams.set("response_type", "code");
url.searchParams.set("scope", "openid profile email memberships:read");
url.searchParams.set("code_challenge", challenge);
url.searchParams.set("code_challenge_method", "S256");
url.searchParams.set("state", state);

// Token exchange (client_secret_post) and userinfo
const token = await fetch(d.token_endpoint, {
  method: "POST",
  headers: { "content-type": "application/x-www-form-urlencoded" },
  body: new URLSearchParams({ grant_type: "authorization_code", code, redirect_uri, code_verifier: verifier,
    client_id: process.env.PRESENCE_CLIENT_ID!, client_secret: process.env.PRESENCE_CLIENT_SECRET! }),
}).then((r) => r.json());
const me = await fetch(d.userinfo_endpoint, { headers: { Authorization: `Bearer ${token.access_token}` } }).then((r) => r.json());
// { sub, name, email, email_verified, picture, memberships: [{ org, role, ... }] }

Registering a client

Your sandbox comes with a client. For production, register the application's redirect URI from the developer console in the shell; you receive a client_id and client_secret. Public clients (native and single-page apps) use PKCE without a secret.

Placeholder: the developer console is being built; until it ships, production clients are registered by the Presence+ team on request.