Skip to content

Product documentation

Identity service configuration

Generated reference for the oauthy.* configuration bound by Identity service.

Identity service configuration

Thoryn's own OIDC federation member: login, registration, password reset, magic link, WebAuthn, lockout, sessions, and user import.

oauthy.identity

Owner: federation-members/identity-service · Source: servers/federation-members/identity-service/src/main/kotlin/com/devnow/oauthy/identity/config/IdentityServiceProperties.kt

PropertyTypeDefaultDescription
oauthy.identity.clients[].client-idString"hub-client"
oauthy.identity.clients[].client-secretString"hub-secret"
oauthy.identity.clients[].redirect-uriString"http://localhost:8080/login/oauth2/code/identity-service"
oauthy.identity.clients[].redirect-uri-patternsList<String>(empty)SSO-1166: optional wildcard-pattern allowlist of redirect URIs accepted in addition to redirectUri.
oauthy.identity.clients[].post-logout-redirect-urisList<String>(empty)SSO-2307: exact-match allow-list of OIDC RP-initiated-logout post_logout_redirect_uri values accepted for this client.
oauthy.identity.webauthn.rp-idString"localhost"
oauthy.identity.webauthn.rp-nameString"Thoryn Identity Service"
oauthy.identity.public-base-urlString""SSO-1134: public origin (scheme://host[:port]) of this identity-service instance.

oauthy.identity.account-unlock

Owner: federation-members/identity-service · Source: servers/federation-members/identity-service/src/main/kotlin/com/devnow/oauthy/identity/unlock/AccountUnlockProperties.kt

SSO-1905: configuration for the self-service account-unlock endpoints (POST /account/unlock/{request,confirm}).

PropertyTypeDefaultDescription
oauthy.identity.account-unlock.token-ttlDurationDuration.ofMinutes(15)How long a freshly-minted unlock token is valid.
oauthy.identity.account-unlock.from-addressString"no-reply@thoryn.io"From address for outbound unlock mail.

oauthy.identity.anonymizer

Owner: federation-members/identity-service · Source: servers/federation-members/identity-service/src/main/kotlin/com/devnow/oauthy/identity/risk/AnonymizerProperties.kt

SSO-2043: configuration for the anonymizer / VPN / Tor / proxy login detector.

PropertyTypeDefaultDescription
oauthy.identity.anonymizer.enabledBooleantrueMaster enable flag for the anonymizer detector.

oauthy.identity.attribute-schema.validation

Owner: federation-members/identity-service · Source: servers/federation-members/identity-service/src/main/kotlin/com/devnow/oauthy/identity/attributeschema/AttributeSchemaValidationProperties.kt

SSO-2541 — configuration for ProductApiAttributeSchemaValidationClient, the identity→product-api seam that validates a user-metadata write against the tenant's attribute schema.

PropertyTypeDefaultDescription
oauthy.identity.attribute-schema.validation.enabledBooleantrueenabled gates the real client.
oauthy.identity.attribute-schema.validation.product-api-base-urlString"http://thoryn-product-api:8082"In-cluster product-api Service base URL.
oauthy.identity.attribute-schema.validation.request-timeoutDurationDuration.ofSeconds(5)Connect + read timeout for the validate call (CLAUDE.md 5s HTTP-to-service ceiling).

oauthy.identity.bot-detection

Owner: federation-members/identity-service · Source: servers/federation-members/identity-service/src/main/kotlin/com/devnow/oauthy/identity/bot/BotDetectionProperties.kt

SSO-2616 — deploy-time (NON per-tenant) configuration for the bot-detection challenge integration.

PropertyTypeDefaultDescription
oauthy.identity.bot-detection.enabledBooleantruePlatform kill-switch.
oauthy.identity.bot-detection.siteverify-urlStringDEFAULT_SITEVERIFY_URLCloudflare Turnstile server-side siteverify endpoint.
oauthy.identity.bot-detection.verify-timeoutDurationDuration.ofSeconds(5)Bound on the siteverify call.
oauthy.identity.bot-detection.fail-open-on-vendor-outageBooleantrueVendor-outage posture.

oauthy.identity.client-email-bases

Owner: federation-members/identity-service · Source: servers/federation-members/identity-service/src/main/kotlin/com/devnow/oauthy/identity/email/ClientEmailBaseProperties.kt

SSO-1787: operator-controlled, per-client override for the base URL the password-reset and email-verification email links are built from.

PropertyTypeDefaultDescription
oauthy.identity.client-email-bases.basesMap<String, String>(empty)Map of client_id → public base URL (scheme://host[:port]) for that client's email links.

oauthy.identity.client-ip

Owner: federation-members/identity-service · Source: servers/federation-members/identity-service/src/main/kotlin/com/devnow/oauthy/identity/web/ClientIpResolutionProperties.kt

SSO-2215 (Item 1): configuration for TrustedClientIpResolver.

PropertyTypeDefaultDescription
oauthy.identity.client-ip.trusted-proxy-countInt1Number of trusted reverse proxies that sit in front of identity-service.

oauthy.identity.consent

Owner: federation-members/identity-service · Source: servers/federation-members/identity-service/src/main/kotlin/com/devnow/oauthy/identity/tou/ProductApiConsentClientProperties.kt

SSO-2563 (C4, epic SSO-2559) — configuration for ProductApiConsentClient, the identity→product-api seam that (a) enumerates the tenant/environment's active consent-purpose registry and (b) records an end-user's purpose-consent grant duri...

PropertyTypeDefaultDescription
oauthy.identity.consent.product-api-base-urlString"http://thoryn-product-api:8082"In-cluster product-api Service base URL.
oauthy.identity.consent.request-timeoutDurationDuration.ofSeconds(5)Connect + read timeout for the consent calls (CLAUDE.md 5s HTTP-to-service ceiling).

oauthy.identity.credential-stuffing

Owner: federation-members/identity-service · Source: servers/federation-members/identity-service/src/main/kotlin/com/devnow/oauthy/identity/credentialstuffing/CredentialStuffingProperties.kt

SSO-71: configuration for credential-stuffing velocity detection on the login endpoint.

PropertyTypeDefaultDescription
oauthy.identity.credential-stuffing.enabledBooleanfalseMaster switch. Defaults to false (opt-in per environment).
oauthy.identity.credential-stuffing.per-ip-failure-thresholdIntDEFAULT_PER_IP_THRESHOLDNumber of failed logins from a single source IP within perIpWindow that trips the per-IP credential-stuffing signal.
oauthy.identity.credential-stuffing.per-ip-windowDurationDEFAULT_PER_IP_WINDOWRolling window for the per-IP failed-login counter.
oauthy.identity.credential-stuffing.per-ip-reputation-failure-thresholdIntDEFAULT_PER_IP_REPUTATION_THRESHOLDSSO-2046: the lowered per-IP failure threshold applied when the source IP carries a known bad reputation (today: a Tor exit node, per the com.devnow.oauthy.identity.reputation.IpReputationService feed).
oauthy.identity.credential-stuffing.per-account-distinct-ip-thresholdIntDEFAULT_PER_ACCOUNT_DISTINCT_IP_THRESHOLDNumber of distinct source IPs that must fail against one account within perAccountWindow to trip the per-account signal.
oauthy.identity.credential-stuffing.per-account-windowDurationDEFAULT_PER_ACCOUNT_WINDOWRolling window for the per-account distinct-IP set.
oauthy.identity.credential-stuffing.soft-block-base-durationDurationDEFAULT_SOFT_BLOCK_BASEBase temporary soft-block duration applied to an IP on its first per-IP breach.
oauthy.identity.credential-stuffing.soft-block-max-durationDurationDEFAULT_SOFT_BLOCK_MAXCeiling on the escalated per-IP soft-block duration.

oauthy.identity.default-tenant-migration

Owner: federation-members/identity-service · Source: servers/federation-members/identity-service/src/main/kotlin/com/devnow/oauthy/identity/defaulttenant/DefaultTenantMigrationProperties.kt

SSO-1979 (epic SSO-1977: deprecate the shared default tenant) — the master gate for the S2 re-key machinery.

PropertyTypeDefaultDescription
oauthy.identity.default-tenant-migration.enabledBooleanfalsetrue activates the lazy-trickle-at-login re-key; false (default) makes the whole seam inert.

oauthy.identity.email-templates

Owner: federation-members/identity-service · Source: servers/federation-members/identity-service/src/main/kotlin/com/devnow/oauthy/identity/emailtemplates/EmailTemplateProperties.kt

SSO-1898: size caps for tenant-supplied email-template overrides.

PropertyTypeDefaultDescription
oauthy.identity.email-templates.max-subject-charsInt998Maximum length (in characters) of a customized subject line.
oauthy.identity.email-templates.max-body-bytesInt64 * 1024Maximum size (in UTF-8 bytes) of a customized HTML body.

oauthy.identity.erasure

Owner: federation-members/identity-service · Source: servers/federation-members/identity-service/src/main/kotlin/com/devnow/oauthy/identity/gdpr/ProductApiErasureClientProperties.kt

SSO-2511 — configuration for ProductApiErasureClient, the identity→product-api seam that opens a two-phase conditional-erasure request when a user erases their own account (DELETE /account/erase).

PropertyTypeDefaultDescription
oauthy.identity.erasure.product-api-base-urlString"http://thoryn-product-api:8082"In-cluster product-api Service base URL.
oauthy.identity.erasure.request-timeoutDurationDuration.ofSeconds(5)Connect + read timeout for the erasure-open call (CLAUDE.md 5s HTTP-to-service ceiling).

oauthy.identity.geo-blocking

Owner: federation-members/identity-service · Source: servers/federation-members/identity-service/src/main/kotlin/com/devnow/oauthy/identity/geoblocking/GeoBlockingProperties.kt

SSO-2042: operator-level configuration for geo-blocking login enforcement.

PropertyTypeDefaultDescription
oauthy.identity.geo-blocking.enabledBooleantrueMaster switch. true (default) → the tenant's stored policy is honoured.
oauthy.identity.geo-blocking.fail-open-on-unresolvedBooleantrueWhat to do when the login IP cannot be resolved to a country (a private / loopback IP, or a public IP the bundled GeoLite2-Country dataset does not map).

oauthy.identity.hooks.delivery-report

Owner: federation-members/identity-service · Source: servers/federation-members/identity-service/src/main/kotlin/com/devnow/oauthy/identity/hooks/HookDeliveryReportProperties.kt

SSO-1985 — config for the inline-stage delivery-report outbox + poller.

PropertyTypeDefaultDescription
oauthy.identity.hooks.delivery-report.enabledBooleantrueWhen false, reports are neither enqueued nor delivered (test / opt-out).
oauthy.identity.hooks.delivery-report.endpointString"http://thoryn-product-api:8082/admin/hooks/internal/delivery-reports"product-api's in-cluster delivery-report ingest URL (Service DNS, not public ingress).
oauthy.identity.hooks.delivery-report.token-fileString"/var/run/secrets/tokens/audit-emitter"Projected SA token file (audience product-api) — reuses the audit-emitter volume.
oauthy.identity.hooks.delivery-report.batch-sizeInt32Rows drained per poll tick.
oauthy.identity.hooks.delivery-report.max-attemptsInt8Max delivery attempts before the report is dropped (dead-lettered).
oauthy.identity.hooks.delivery-report.max-backoffDurationDuration.ofMinutes(5)Backoff ceiling: next_attempt_at = now + min(maxBackoff, 2^attempts) s.
oauthy.identity.hooks.delivery-report.poll-intervalDurationDuration.ofSeconds(5)Poll cadence.
oauthy.identity.hooks.delivery-report.block-timeoutDurationDuration.ofSeconds(5)Reactive→blocking / HTTP ceiling (CLAUDE.md SSO-771).

oauthy.identity.hooks.registration-pre-create

Owner: federation-members/identity-service · Source: servers/federation-members/identity-service/src/main/kotlin/com/devnow/oauthy/identity/hooks/RegistrationHookProperties.kt

SSO-1957 (Actions/Hooks H3) — engine config for the registration.pre-create inline stage in identity-service.

PropertyTypeDefaultDescription
oauthy.identity.hooks.registration-pre-create.default-budgetDurationDuration.ofMillis(3000)Applied when the mirrored hook carries no timeoutMs.
oauthy.identity.hooks.registration-pre-create.hard-capDurationDuration.ofMillis(5000)Absolute ceiling any per-hook timeoutMs is clamped to (SSO-771 ≤ 5 s).
oauthy.identity.hooks.registration-pre-create.max-request-bytesInt64 * 1024Max request body bytes sent to the tenant endpoint (DoS bound).
oauthy.identity.hooks.registration-pre-create.max-response-bytesInt64 * 1024Max response body bytes read back from the tenant endpoint (DoS bound).
oauthy.identity.hooks.registration-pre-create.max-attribute-charsInt256Per-augmented-attribute character cap after merge (DoS / abuse bound).

oauthy.identity.hooks.user-created

Owner: federation-members/identity-service · Source: servers/federation-members/identity-service/src/main/kotlin/com/devnow/oauthy/identity/hooks/UserCreatedEventProperties.kt

SSO-1957 (Actions/Hooks H3) — config for the user.created event outbox + poller.

PropertyTypeDefaultDescription
oauthy.identity.hooks.user-created.enabledBooleantrueWhen false, events are neither enqueued nor delivered (test / opt-out).
oauthy.identity.hooks.user-created.endpointString"http://thoryn-product-api:8082/admin/hooks/internal/events"product-api's in-cluster ingest URL (Service DNS, not the public ingress).
oauthy.identity.hooks.user-created.token-fileString"/var/run/secrets/tokens/audit-emitter"Projected SA token file (audience product-api) — reuses the audit-emitter volume.
oauthy.identity.hooks.user-created.batch-sizeInt32Rows drained per poll tick.
oauthy.identity.hooks.user-created.max-attemptsInt8Max delivery attempts before the event is dropped (dead-lettered).
oauthy.identity.hooks.user-created.max-backoffDurationDuration.ofMinutes(5)Backoff ceiling: next_attempt_at = now + min(maxBackoff, 2^attempts) s.
oauthy.identity.hooks.user-created.poll-intervalDurationDuration.ofSeconds(5)Poll cadence.
oauthy.identity.hooks.user-created.block-timeoutDurationDuration.ofSeconds(5)Reactive→blocking / HTTP ceiling (CLAUDE.md SSO-771).

Owner: federation-members/identity-service · Source: servers/federation-members/identity-service/src/main/kotlin/com/devnow/oauthy/identity/consent/HubConsentClientProperties.kt

SSO-2564 (C5, epic SSO-2559) — configuration for HubConsentClient, the identity→hub seam that reads and withdraws an end-user's OAuth-client consents for the "manage my consents" account page.

PropertyTypeDefaultDescription
oauthy.identity.hub-consent.hub-base-urlString"http://thoryn-hub:8080"In-cluster hub Service base URL.
oauthy.identity.hub-consent.sa-token-pathString"/var/run/secrets/tokens/hub-internal-auth"On-disk path of identity-service's projected SA token for the identity → hub channel (audience hub-internal-auth).
oauthy.identity.hub-consent.request-timeoutDurationDuration.ofSeconds(5)Connect + read timeout (CLAUDE.md 5s HTTP-to-service ceiling).

oauthy.identity.impersonation-notification

Owner: federation-members/identity-service · Source: servers/federation-members/identity-service/src/main/kotlin/com/devnow/oauthy/identity/impersonation/ImpersonationNotificationProperties.kt

SSO-2608 (epic SSO-2594, ADR 2026-08-17-user-impersonation-trust-model.md §7) — configuration for the ImpersonationNotificationEmailSender that emails an impersonated user "your account was accessed by support".

PropertyTypeDefaultDescription
oauthy.identity.impersonation-notification.enabledBooleantrueMaster switch. When false, the sender returns immediately without dispatching.
oauthy.identity.impersonation-notification.from-addressString"no-reply@thoryn.io"From address for the notification.
oauthy.identity.impersonation-notification.public-base-urlString"https://stg.thoryn.org"Public origin for the optional "review your account activity" link.

oauthy.identity.import.jobs

Owner: federation-members/identity-service · Source: servers/federation-members/identity-service/src/main/kotlin/com/devnow/oauthy/identity/users/importjob/BulkImportJobProperties.kt

SSO-1906: configuration for the asynchronous bulk user-import job surface.

PropertyTypeDefaultDescription
oauthy.identity.import.jobs.max-rowsInt10_000Upper bound on rows per asynchronous job.
oauthy.identity.import.jobs.chunk-sizeInt100Rows imported per progress-update slice.
oauthy.identity.import.jobs.jobs-per-tickInt3Pending jobs drained per poller tick.
oauthy.identity.import.jobs.max-row-errorsInt500Cap on persisted per-row errors per job.
oauthy.identity.import.jobs.max-attemptsInt5Attempts before a job that keeps failing transiently (e.g.
oauthy.identity.import.jobs.stale-afterDurationDuration.ofMinutes(30)An in_progress job older than this is presumed orphaned by a dead runner and re-queued.
oauthy.identity.import.jobs.transit-keyString"identity-bulk-import"Vault Transit key the payload envelope is encrypted against.

oauthy.identity.impossible-travel

Owner: federation-members/identity-service · Source: servers/federation-members/identity-service/src/main/kotlin/com/devnow/oauthy/identity/risk/ImpossibleTravelProperties.kt

SSO-72: configuration for the speed-based impossible-travel detector.

PropertyTypeDefaultDescription
oauthy.identity.impossible-travel.enabledBooleantrueMaster enable flag for the speed-based detector.
oauthy.identity.impossible-travel.max-kmhDouble1000.0Maximum plausible travel speed in km/h.
oauthy.identity.impossible-travel.min-distance-kmDouble100.0Minimum great-circle distance (km) between the two sign-in locations required before the speed check is even considered.

oauthy.identity.impossible-travel-response

Owner: federation-members/identity-service · Source: servers/federation-members/identity-service/src/main/kotlin/com/devnow/oauthy/identity/risk/response/ImpossibleTravelResponseProperties.kt

SSO-2041: operator-level configuration for the impossible-travel enforcement response.

PropertyTypeDefaultDescription
oauthy.identity.impossible-travel-response.enabledBooleantrueMaster switch. true (default) → the tenant's stored response policy is honoured.

oauthy.identity.internal-auth.k8s

Owner: federation-members/identity-service · Source: servers/federation-members/identity-service/src/main/kotlin/com/devnow/oauthy/identity/internalauth/InternalAuthK8sProperties.kt

SSO-1792 — configuration for the secret-less S2S credential-validation channel (POST /internal/auth/validate).

PropertyTypeDefaultDescription
oauthy.identity.internal-auth.k8s.enabledBooleanfalseGate for the whole channel.
oauthy.identity.internal-auth.k8s.issuerString"https://kubernetes.default.svc.cluster.local"Expected iss claim — the cluster SA issuer (SSO-1477).
oauthy.identity.internal-auth.k8s.jwks-uriString"https://kubernetes.default.svc/openid/v1/jwks"In-cluster OIDC JWKS address the decoder fetches the cluster keys from.
oauthy.identity.internal-auth.k8s.audienceString"identity-internal-auth"Audience the hub's credential-validation projected token is pinned to (identity-internal-auth).
oauthy.identity.internal-auth.k8s.allowed-subString"^system:serviceaccount:thoryn:thoryn-hub-(audit-emitter|workload)$"SSO-1790 anchored caller allow-list, a SINGLE string in the identity-service config shape (the shared factory takes a List<String>; see toValidationProperties).
oauthy.identity.internal-auth.k8s.ca-pathString"/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"Cluster CA bundle path for the JWKS TLS trust (SSO-1468).
oauthy.identity.internal-auth.k8s.token-pathString"/var/run/secrets/kubernetes.io/serviceaccount/token"Projected SA token presented as a Bearer on the JWKS fetch (SSO-1471).
oauthy.identity.internal-auth.k8s.connect-timeoutDurationDuration.ofSeconds(2)Connect timeout for the JWKS fetch (SSO-1468 blast-radius bound).
oauthy.identity.internal-auth.k8s.read-timeoutDurationDuration.ofSeconds(2)Read timeout for the JWKS fetch (SSO-1468 blast-radius bound).

oauthy.identity.invitations

Owner: federation-members/identity-service · Source: servers/federation-members/identity-service/src/main/kotlin/com/devnow/oauthy/identity/invitations/InvitationProperties.kt

SSO-65: configuration for the admin user-invitation flow.

PropertyTypeDefaultDescription
oauthy.identity.invitations.site-originString"https://thoryn.org"Public origin of the marketing site / console that hosts the invitation landing page.
oauthy.identity.invitations.token-ttlDurationDuration.ofDays(7)Default TTL for a freshly-issued invitation.
oauthy.identity.invitations.from-addressString"no-reply@thoryn.io"From address for outbound invitation mail.

oauthy.identity.invitations.federated-accept

Owner: federation-members/identity-service · Source: servers/federation-members/identity-service/src/main/kotlin/com/devnow/oauthy/identity/invitations/federated/FederatedInvitationBeginProperties.kt

SSO-2399 (slice 4b of SSO-2384 / D2 federated-invitation-accept) — configuration for the front door of the federated-invitation-accept flow: identity-service's POST /invitations/{token}/federated/begin (FederatedInvitationBeginController).

PropertyTypeDefaultDescription
oauthy.identity.invitations.federated-accept.hub-base-urlString"http://thoryn-hub:8080"In-cluster base URL of the authorization hub, for the identity → hub mint call (POST /internal/account-link-intents).
oauthy.identity.invitations.federated-accept.sa-token-pathString"/var/run/secrets/tokens/hub-internal-auth"On-disk path of identity-service's projected Kubernetes ServiceAccount token for the identity → hub mint channel (audience hub-internal-auth), mounted by the chart at /var/run/secrets/tokens/hub-internal-auth (a second serviceAccountToke...
oauthy.identity.invitations.federated-accept.allowed-return-originsList<String>(empty)Open-redirect allowlist for the accept-page returnUri the caller supplies.

oauthy.identity.ip-reputation

Owner: federation-members/identity-service · Source: servers/federation-members/identity-service/src/main/kotlin/com/devnow/oauthy/identity/reputation/IpReputationProperties.kt

SSO-2046: configuration for the IP-reputation feed that lowers the SSO-71 credential-stuffing per-IP threshold for a known-bad-reputation source IP.

PropertyTypeDefaultDescription
oauthy.identity.ip-reputation.enabledBooleanfalseMaster switch. Defaults to false (opt-in per environment).
oauthy.identity.ip-reputation.tor-exit-list-urlStringDEFAULT_TOR_EXIT_LIST_URLAuthoritative, free source for the Tor exit-node list — the Tor Project's published bulk exit list (one IP per line; # comments and blank lines are ignored).
oauthy.identity.ip-reputation.refresh-intervalDurationDEFAULT_REFRESH_INTERVALHow often the scheduled fetch refreshes the list.
oauthy.identity.ip-reputation.feed-ttlDurationDEFAULT_FEED_TTLTTL applied to the stored feed.
oauthy.identity.ip-reputation.request-timeoutDurationDEFAULT_REQUEST_TIMEOUTConnect + read timeout for the outbound fetch.
oauthy.identity.ip-reputation.max-entriesIntDEFAULT_MAX_ENTRIESDefensive cap on how many entries a single refresh will store — bounds the blast radius of a malformed or hostile oversized response.

oauthy.identity.lockout

Owner: federation-members/identity-service · Source: servers/federation-members/identity-service/src/main/kotlin/com/devnow/oauthy/identity/lockout/AccountLockoutProperties.kt

SSO-1895: tenant-configurable policy for the per-account password-failure lockout.

PropertyTypeDefaultDescription
oauthy.identity.lockout.enabledBooleantrueMaster switch. When false the lockout is inert: failures are never counted, accounts are never locked, and AccountLockoutService short- circuits every call.
oauthy.identity.lockout.thresholdIntDEFAULT_THRESHOLDPlatform-default consecutive-failure threshold.
oauthy.identity.lockout.cooldownDurationDEFAULT_COOLDOWNPlatform-default lock duration once the threshold is reached.
oauthy.identity.lockout.failure-windowDurationDEFAULT_COOLDOWNHow long the consecutive-failure counter itself survives without a new failure.
oauthy.identity.lockout.tenant-overridesMap<String, TenantLockoutPolicy>(empty)Per-tenant overrides keyed by tenant_id.

oauthy.identity.login-flow

Owner: federation-members/identity-service · Source: servers/federation-members/identity-service/src/main/kotlin/com/devnow/oauthy/identity/loginflow/LoginFlowProperties.kt

SSO-2334: configuration for the login-flow execution engine (epic SSO-1889, ADR 2026-08-05-login-flow-model-and-execution-engine.md).

PropertyTypeDefaultDescription
oauthy.identity.login-flow.modeLoginFlowModeLoginFlowMode.OFFthe engine's runtime mode on the native login path.

oauthy.identity.magic-code

Owner: federation-members/identity-service · Source: servers/federation-members/identity-service/src/main/kotlin/com/devnow/oauthy/identity/magiccode/MagicCodeProperties.kt

SSO-2595 (epic SSO-2581): configuration for the magic-CODE (passwordless 6-digit email OTP) sign-in path — the WorkOS-style sibling of the SSO-57 magic-LINK flow.

PropertyTypeDefaultDescription
oauthy.identity.magic-code.enabledBooleantrueWhether the magic-code affordance is enabled globally.
oauthy.identity.magic-code.code-lengthInt6Number of digits in the emailed code.
oauthy.identity.magic-code.token-ttlDurationDuration.ofMinutes(10)How long a freshly-minted magic-code is valid.
oauthy.identity.magic-code.max-attemptsInt5Maximum number of verify attempts against a single issued code before it is voided.
oauthy.identity.magic-code.from-addressString"no-reply@thoryn.io"From address for outbound magic-code mail.

Owner: federation-members/identity-service · Source: servers/federation-members/identity-service/src/main/kotlin/com/devnow/oauthy/identity/magiclink/MagicLinkProperties.kt

SSO-57: configuration for the magic-link (passwordless) sign-in path.

PropertyTypeDefaultDescription
oauthy.identity.magic-link.public-base-urlString"http://localhost:9100"Public origin where this identity-service is reachable from the user's browser.
oauthy.identity.magic-link.token-ttlDurationDuration.ofMinutes(15)How long a freshly-minted magic-link token is valid.
oauthy.identity.magic-link.from-addressString"no-reply@thoryn.io"From address for outbound magic-link mail.
oauthy.identity.magic-link.enabledBooleantrueWhether the magic-link affordance is enabled globally.
oauthy.identity.magic-link.cross-device.code-lengthInt6Digits in the cross-device continuation code.
oauthy.identity.magic-link.cross-device.code-ttlDurationDuration.ofMinutes(10)How long a cross-device continuation code is valid.
oauthy.identity.magic-link.cross-device.max-attemptsInt5Maximum wrong-code redeem attempts against a single continuation code before it is voided.

oauthy.identity.migration.import

Owner: federation-members/identity-service · Source: servers/federation-members/identity-service/src/main/kotlin/com/devnow/oauthy/identity/migration/importjob/MigrationImportJobProperties.kt

SSO-2358 — configuration for the eager Auth0 profile-import job surface.

PropertyTypeDefaultDescription
oauthy.identity.migration.import.page-sizeInt50Profiles fetched per page from the source's export API.
oauthy.identity.migration.import.jobs-per-tickInt2Pending jobs claimed + processed per poller tick.
oauthy.identity.migration.import.max-pages-per-claimInt10Pages drained per claim before the job is checkpointed back to pending for a later tick.
oauthy.identity.migration.import.max-attemptsInt5Consecutive transient failures (source unreachable / Management token unobtainable) before a job flips to failed instead of re-queueing.
oauthy.identity.migration.import.stale-afterDurationDuration.ofMinutes(15)An in_progress job older than this is presumed orphaned by a dead runner and re-queued.

oauthy.identity.new-device

Owner: federation-members/identity-service · Source: servers/federation-members/identity-service/src/main/kotlin/com/devnow/oauthy/identity/risk/NewDeviceProperties.kt

SSO-2044: configuration for the new-device login detector.

PropertyTypeDefaultDescription
oauthy.identity.new-device.enabledBooleantrueMaster enable flag for the new-device detector.

oauthy.identity.notifications.new-country

Owner: federation-members/identity-service · Source: servers/federation-members/identity-service/src/main/kotlin/com/devnow/oauthy/identity/notifications/NewCountryLoginProperties.kt

SSO-891 configuration for the "login from new country" notification.

PropertyTypeDefaultDescription
oauthy.identity.notifications.new-country.enabledBooleantrueMaster switch. When false, the NewCountryLoginNotifier returns immediately without sending anything.
oauthy.identity.notifications.new-country.from-addressString"no-reply@thoryn.io"From address for the outbound notification.
oauthy.identity.notifications.new-country.public-base-urlString"https://stg.thoryn.org"Public origin used to build the "wasn't me" link.
oauthy.identity.notifications.new-country.suppression-windowDurationDuration.ofHours(24)24h per-(user, country) suppression window — a second login from the same country inside this window does not fire a duplicate email.
oauthy.identity.notifications.new-country.wasnt-me-ttlDurationDuration.ofHours(24)TTL for the single-use "wasn't me" link.

oauthy.identity.password-reset

Owner: federation-members/identity-service · Source: servers/federation-members/identity-service/src/main/kotlin/com/devnow/oauthy/identity/passwordreset/PasswordResetApiProperties.kt

SSO-1022: configuration for the JSON /password/reset/{request,confirm} endpoints.

PropertyTypeDefaultDescription
oauthy.identity.password-reset.site-originString"https://thoryn.org"Public origin of the marketing site that hosts the /reset-password?token=...
oauthy.identity.password-reset.token-ttlDurationDuration.ofMinutes(15)How long a freshly-minted reset token is valid.
oauthy.identity.password-reset.from-addressString"no-reply@thoryn.io"From address for outbound password-reset mail.

oauthy.identity.scim.directory-bearer

Owner: federation-members/identity-service · Source: servers/federation-members/identity-service/src/main/kotlin/com/devnow/oauthy/identity/scim/directory/DirectoryScimBearerAuthProperties.kt

SSO-2490 — configuration for enforcing the inbound Directory-Sync SCIM bearer at the SCIM ingest edge.

PropertyTypeDefaultDescription
oauthy.identity.scim.directory-bearer.enabledBooleantrueMaster switch. Default on: the enforcement only activates on the per-directory ingest path (/scim/v2/directories/[]), which carries no traffic until a tenant configures a Directory-Sync connector, so enabling it by default changes no exi...
oauthy.identity.scim.directory-bearer.product-api-base-urlString"http://thoryn-product-api:8082"In-cluster product-api Service base URL (matches the SCIM-groups facade / audit-emitter).
oauthy.identity.scim.directory-bearer.request-timeoutDurationDuration.ofSeconds(5)Connect + read timeout for the resolve / record-push S2S calls (CLAUDE.md 5s HTTP-to-service ceiling).

oauthy.identity.scim.org-unification

Owner: federation-members/identity-service · Source: servers/federation-members/identity-service/src/main/kotlin/com/devnow/oauthy/identity/scim/groups/ScimOrgUnificationProperties.kt

Configuration for the SCIM-Groups unification facade (SSO-1959).

PropertyTypeDefaultDescription
oauthy.identity.scim.org-unification.product-api-base-urlString"http://thoryn-product-api:8082"In-cluster product-api Service base URL (matches the audit-emitter's thoryn-product-api:8082).
oauthy.identity.scim.org-unification.request-timeoutDurationDuration.ofSeconds(5)Connect + read timeout for the facade HTTP calls (CLAUDE.md 5s HTTP-to-service ceiling).
oauthy.identity.scim.org-unification.backfill-enabledBooleantrueWhether the one-time idempotent backfill runs on startup (reads the legacy organisations / organisation_memberships and upserts them into the product-api SoR).

oauthy.identity.security-incident-email

Owner: federation-members/identity-service · Source: servers/federation-members/identity-service/src/main/kotlin/com/devnow/oauthy/identity/security/SecurityIncidentEmailProperties.kt

SSO-1109: configuration for the SecurityIncidentEmailSender that ships a "refresh-token theft detected" notification when the hub's SecurityEventForwarder POSTs a refresh-token-replay event to POST /internal/security-events.

PropertyTypeDefaultDescription
oauthy.identity.security-incident-email.enabledBooleantrueMaster switch. When false, the sender returns immediately without dispatching.
oauthy.identity.security-incident-email.from-addressString"no-reply@thoryn.io"From address for the outbound notification.
oauthy.identity.security-incident-email.public-base-urlString"https://stg.thoryn.org"Public origin used to build the "wasn't me" link.
oauthy.identity.security-incident-email.suppression-windowDurationDuration.ofHours(1)Per-user suppression window.
oauthy.identity.security-incident-email.wasnt-me-ttlDurationDuration.ofDays(7)SSO-1111: TTL for the single-use "wasn't me" link embedded in the refresh-token-theft email.

oauthy.identity.security-policy

Owner: federation-members/identity-service · Source: servers/federation-members/identity-service/src/main/kotlin/com/devnow/oauthy/identity/securitypolicy/SecurityPolicyProperties.kt

SSO-1930: platform security floor + defaults for the tenant credential-security policy (ADR 2026-07-14-tenant-security-policy-facade.md §1).

PropertyTypeDefaultDescription
oauthy.identity.security-policy.floor.lockout-threshold-minInt3
oauthy.identity.security-policy.floor.lockout-threshold-maxInt10
oauthy.identity.security-policy.floor.lockout-threshold-defaultInt5
oauthy.identity.security-policy.floor.lockout-cooldown-seconds-minInt60
oauthy.identity.security-policy.floor.lockout-cooldown-seconds-defaultInt900
oauthy.identity.security-policy.floor.password-min-length-minInt8
oauthy.identity.security-policy.floor.password-min-length-defaultInt8
oauthy.identity.security-policy.floor.password-require-complexity-defaultBooleanfalse
oauthy.identity.security-policy.floor.password-require-uppercase-defaultBooleanfalse
oauthy.identity.security-policy.floor.password-require-lowercase-defaultBooleanfalse
oauthy.identity.security-policy.floor.password-require-number-defaultBooleanfalse
oauthy.identity.security-policy.floor.password-require-special-defaultBooleanfalse
oauthy.identity.security-policy.floor.password-history-depth-minInt0
oauthy.identity.security-policy.floor.password-history-depth-maxInt24
oauthy.identity.security-policy.floor.password-history-depth-defaultInt0
oauthy.identity.security-policy.floor.password-min-strength-minInt0
oauthy.identity.security-policy.floor.password-min-strength-maxInt4
oauthy.identity.security-policy.floor.password-min-strength-defaultInt0
oauthy.identity.security-policy.floor.max-concurrent-sessions-minInt1
oauthy.identity.security-policy.floor.max-concurrent-sessions-maxInt50
oauthy.identity.security-policy.floor.max-concurrent-sessions-defaultInt10
oauthy.identity.security-policy.floor.self-signup-enabled-defaultBooleantrue
oauthy.identity.security-policy.floor.invite-only-defaultBooleanfalse
oauthy.identity.security-policy.floor.require-verified-email-defaultBooleanfalse
oauthy.identity.security-policy.floor.trusted-device-enabled-defaultBooleanfalse
oauthy.identity.security-policy.floor.trusted-device-max-ttl-secs-minInt3_600
oauthy.identity.security-policy.floor.trusted-device-max-ttl-secs-maxInt2_592_000
oauthy.identity.security-policy.floor.trusted-device-max-ttl-secs-defaultInt2_592_000
oauthy.identity.security-policy.floor.session-idle-timeout-secs-minInt60
oauthy.identity.security-policy.floor.session-idle-timeout-secs-maxInt2_592_000
oauthy.identity.security-policy.floor.session-absolute-timeout-secs-minInt300
oauthy.identity.security-policy.floor.session-absolute-timeout-secs-maxInt7_776_000
oauthy.identity.security-policy.breach-check.enabledBooleantrueWhether a password offered at registration / password-reset is checked against the HIBP breach corpus.
oauthy.identity.security-policy.breach-check.fail-openBooleantrueWhat to do when HIBP cannot be reached (timeout / 5xx / DNS / SSRF guard).

oauthy.identity.sessions

Owner: federation-members/identity-service · Source: servers/federation-members/identity-service/src/main/kotlin/com/devnow/oauthy/identity/sessions/SessionLimitProperties.kt

SSO-890: configuration for the per-user concurrent-session cap.

PropertyTypeDefaultDescription
oauthy.identity.sessions.max-per-userInt10Maximum number of concurrent active sessions per user.

oauthy.identity.sign-in-attempts

Owner: federation-members/identity-service · Source: servers/federation-members/identity-service/src/main/kotlin/com/devnow/oauthy/identity/signin/SignInAttemptProperties.kt

SSO-1908: configuration for the sign_in_attempt capture table.

PropertyTypeDefaultDescription
oauthy.identity.sign-in-attempts.retentionDurationDuration.ofDays(365)How long to retain sign_in_attempt rows.
oauthy.identity.sign-in-attempts.factor-ttlDurationDuration.ofMinutes(15)TTL of the transient last-login-factor stash in Redis (LastLoginFactorStore).

oauthy.identity.social

Owner: federation-members/identity-service · Source: servers/federation-members/identity-service/src/main/kotlin/com/devnow/oauthy/identity/social/runtime/SocialRuntimeProperties.kt

SSO-2611 (epic SSO-2606): configuration for the social-login OAuth RUNTIME — the engine that turns a per-tenant com.devnow.oauthy.identity.social.SocialConnectorConfigEntity row into an actual "Sign in with Google" OAuth dance on the hos...

PropertyTypeDefaultDescription
oauthy.identity.social.enabledBooleantrueGlobal feature switch for the social-login runtime.
oauthy.identity.social.redirect-base-urlString""Browser-facing public origin the provider redirects back to.
oauthy.identity.social.state-ttlDurationDuration.ofMinutes(10)How long a pending authorize state lives in Redis before it expires.
oauthy.identity.social.http-timeoutDurationDuration.ofSeconds(5)Bounded HTTP timeout for every outbound provider call (discovery, token, userinfo, JWKS).
oauthy.identity.social.google.issuerString""
oauthy.identity.social.google.authorization-endpointString""
oauthy.identity.social.google.token-endpointString""
oauthy.identity.social.google.jwks-uriString""
oauthy.identity.social.google.user-info-endpointString""
oauthy.identity.social.microsoft.issuerString""
oauthy.identity.social.microsoft.authorization-endpointString""
oauthy.identity.social.microsoft.token-endpointString""
oauthy.identity.social.microsoft.jwks-uriString""
oauthy.identity.social.microsoft.user-info-endpointString""
oauthy.identity.social.github.issuerString""
oauthy.identity.social.github.authorization-endpointString""
oauthy.identity.social.github.token-endpointString""
oauthy.identity.social.github.jwks-uriString""
oauthy.identity.social.github.user-info-endpointString""
oauthy.identity.social.gitlab.issuerString""
oauthy.identity.social.gitlab.authorization-endpointString""
oauthy.identity.social.gitlab.token-endpointString""
oauthy.identity.social.gitlab.jwks-uriString""
oauthy.identity.social.gitlab.user-info-endpointString""
oauthy.identity.social.salesforce.issuerString""
oauthy.identity.social.salesforce.authorization-endpointString""
oauthy.identity.social.salesforce.token-endpointString""
oauthy.identity.social.salesforce.jwks-uriString""
oauthy.identity.social.salesforce.user-info-endpointString""
oauthy.identity.social.intuit.issuerString""
oauthy.identity.social.intuit.authorization-endpointString""
oauthy.identity.social.intuit.token-endpointString""
oauthy.identity.social.intuit.jwks-uriString""
oauthy.identity.social.intuit.user-info-endpointString""
oauthy.identity.social.xero.issuerString""
oauthy.identity.social.xero.authorization-endpointString""
oauthy.identity.social.xero.token-endpointString""
oauthy.identity.social.xero.jwks-uriString""
oauthy.identity.social.xero.user-info-endpointString""
oauthy.identity.social.linkedin.issuerString""
oauthy.identity.social.linkedin.authorization-endpointString""
oauthy.identity.social.linkedin.token-endpointString""
oauthy.identity.social.linkedin.jwks-uriString""
oauthy.identity.social.linkedin.user-info-endpointString""
oauthy.identity.social.apple.issuerString""
oauthy.identity.social.apple.authorization-endpointString""
oauthy.identity.social.apple.token-endpointString""
oauthy.identity.social.apple.jwks-uriString""
oauthy.identity.social.apple.user-info-endpointString""
oauthy.identity.social.slack.issuerString""
oauthy.identity.social.slack.authorization-endpointString""
oauthy.identity.social.slack.token-endpointString""
oauthy.identity.social.slack.jwks-uriString""
oauthy.identity.social.slack.user-info-endpointString""
oauthy.identity.social.vercel.issuerString""
oauthy.identity.social.vercel.authorization-endpointString""
oauthy.identity.social.vercel.token-endpointString""
oauthy.identity.social.vercel.jwks-uriString""
oauthy.identity.social.vercel.user-info-endpointString""

oauthy.identity.step-up.email-otp

Owner: federation-members/identity-service · Source: servers/federation-members/identity-service/src/main/kotlin/com/devnow/oauthy/identity/stepup/StepUpChallengeProperties.kt

SSO-2083: platform-level configuration for the out-of-band email step-up challenge that lets identity-service genuinely challenge a FACTOR-LESS user on a high-risk sign-in.

PropertyTypeDefaultDescription
oauthy.identity.step-up.email-otp.enabledBooleantrueMaster switch for the email-OTP step-up channel.
oauthy.identity.step-up.email-otp.code-lengthInt6Digits in the emailed code.
oauthy.identity.step-up.email-otp.ttlDurationDuration.ofMinutes(10)How long an issued code stays valid.
oauthy.identity.step-up.email-otp.max-attemptsInt5Verification attempts allowed against one challenge before it is voided.
oauthy.identity.step-up.email-otp.max-per-user-per-hourLong5Challenges a single user may be issued per hour (Bucket4j + Redis).
oauthy.identity.step-up.email-otp.max-per-ip-per-hourLong20Challenges a single source IP may trigger per hour (Bucket4j + Redis).
oauthy.identity.step-up.email-otp.from-addressString"no-reply@thoryn.io"From-address for the challenge email, mirroring the per-sender convention the other identity-service senders use (magic-link, password-reset, verification).

oauthy.identity.tenant-facade.k8s

Owner: federation-members/identity-service · Source: servers/federation-members/identity-service/src/main/kotlin/com/devnow/oauthy/identity/internaltenant/TenantFacadeK8sProperties.kt

SSO-1928 — configuration for the secret-less S2S auth on the tenant-parameterized facade surface (/internal/tenants/[]), consumed by product-api's login-experience / security-policy / migration-source / invitation / user-management facades.

PropertyTypeDefaultDescription
oauthy.identity.tenant-facade.k8s.enabledBooleanfalseGate for the whole channel.
oauthy.identity.tenant-facade.k8s.issuerString"https://kubernetes.default.svc.cluster.local"Expected iss claim — the cluster SA issuer (SSO-1477).
oauthy.identity.tenant-facade.k8s.jwks-uriString"https://kubernetes.default.svc/openid/v1/jwks"In-cluster OIDC JWKS address the decoder fetches the cluster keys from.
oauthy.identity.tenant-facade.k8s.audienceString"identity-tenant-facade"Audience product-api's facade projected token is pinned to (identity-tenant-facade).
oauthy.identity.tenant-facade.k8s.allowed-subString"^system:serviceaccount:thoryn:thoryn-product-api$"Anchored (^…$) exact-match caller allow-list — product-api's ServiceAccount.
oauthy.identity.tenant-facade.k8s.ca-pathString"/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"Cluster CA bundle path for the JWKS TLS trust (SSO-1468).
oauthy.identity.tenant-facade.k8s.token-pathString"/var/run/secrets/kubernetes.io/serviceaccount/token"Projected SA token presented as a Bearer on the JWKS fetch (SSO-1471).
oauthy.identity.tenant-facade.k8s.connect-timeoutDurationDuration.ofSeconds(2)Connect timeout for the JWKS fetch (SSO-1468 blast-radius bound).
oauthy.identity.tenant-facade.k8s.read-timeoutDurationDuration.ofSeconds(2)Read timeout for the JWKS fetch (SSO-1468 blast-radius bound).

oauthy.identity.tenant-subdomain

Owner: federation-members/identity-service · Source: servers/federation-members/identity-service/src/main/kotlin/com/devnow/oauthy/identity/branding/TenantSubdomainProperties.kt

SSO-2031 (Part B) — configuration for per-tenant identity-subdomain login resolution, mirroring the hub's per-tenant issuer host {slug}.hub.<platformDomain> (SSO-1517).

PropertyTypeDefaultDescription
oauthy.identity.tenant-subdomain.base-domainString""the platform base domain under which per-tenant identity subdomains live — the value without the leading identity.
oauthy.identity.tenant-subdomain.refresh-interval-millisLongDEFAULT_REFRESH_INTERVAL_MILLISthe scheduled cadence (ms) at which IdentityTenantSlugRegistry reloads its trusted-tenant-slug snapshot.

oauthy.identity.trusted-device

Owner: federation-members/identity-service · Source: servers/federation-members/identity-service/src/main/kotlin/com/devnow/oauthy/identity/trusteddevice/TrustedDeviceProperties.kt

SSO-2634: platform-level (operator, NOT tenant-writable) configuration for the trusted-device MFA skip — cookie name, token entropy, and the TTL floor bounds.

PropertyTypeDefaultDescription
oauthy.identity.trusted-device.cookie-nameString"THORYN_TRUSTED_DEVICE"Name of the trusted-device cookie.
oauthy.identity.trusted-device.token-bytesInt32Number of random bytes in the opaque cookie token.

oauthy.identity.user-metadata

Owner: federation-members/identity-service · Source: servers/federation-members/identity-service/src/main/kotlin/com/devnow/oauthy/identity/users/service/UserMetadataProperties.kt

SSO-1894: configuration for the per-user metadata bags (app_metadata + user_metadata).

PropertyTypeDefaultDescription
oauthy.identity.user-metadata.max-bytes-per-bagInt32 * 1024Maximum size, in UTF-8 bytes of the compact JSON serialisation, of a single metadata bag.

oauthy.identity.verification

Owner: federation-members/identity-service · Source: servers/federation-members/identity-service/src/main/kotlin/com/devnow/oauthy/identity/verification/EmailVerificationProperties.kt

Configuration for the email-verification flow (SSO-911).

PropertyTypeDefaultDescription
oauthy.identity.verification.enabledBooleantrueMaster switch. When false, registration succeeds without sending an email and the controller endpoints return 404.
oauthy.identity.verification.token-ttlDurationDuration.ofHours(24)How long the verification link is valid.
oauthy.identity.verification.from-addressString"no-reply@thoryn.io"From address for outbound verification mail.
oauthy.identity.verification.public-base-urlString"https://stg.thoryn.org"Public origin of the marketing site that hosts the verification landing page.
oauthy.identity.verification.resend-rate-limit-per-hourInt3Per-email cap on resend requests inside a sliding one-hour window.

oauthy.risk

Owner: federation-members/identity-service · Source: servers/federation-members/identity-service/src/main/kotlin/com/devnow/oauthy/identity/risk/RiskProperties.kt

SSO-1318: configuration for the risk-signal engine.

PropertyTypeDefaultDescription
oauthy.risk.hibp-api-base-urlString"https://api.pwnedpasswords.com"Base URL of the HIBP (HaveIBeenPwned) k-anonymity password-range API.
oauthy.risk.hibp-cache-ttlDurationDuration.ofHours(24)How long a positive HIBP result is cached per user+prefix in Redis.
oauthy.risk.hibp-request-timeoutDurationDuration.ofSeconds(2)SSO-60: connect + read timeout for a single HIBP range call.
oauthy.risk.impossible-travel-windowDurationDuration.ofHours(2)Window within which two sign-ins from geographically distant locations trigger the impossible-travel signal.
oauthy.risk.impossible-travel-distance-kmDouble500.0Minimum distance in kilometres between two sign-in countries' centroids required to fire the impossible-travel signal.
oauthy.risk.unfamiliar-location-windowDurationDuration.ofDays(90)How far back to look when deciding whether a country/ASN is "unfamiliar" for a user.
oauthy.risk.history-ttlDurationDuration.ofDays(90)How long to retain rows in sign_in_history.

Every property above is set as a YAML key under application.yml or, equivalently, as an environment variable using Spring's relaxed binding (oauthy.hub.platform-client-ids -> OAUTHY_HUB_PLATFORM_CLIENT_IDS). A _required_ default means the service will not start until the value is supplied.