Modstack Identity and User Management
Enterprise-grade authentication and user management for your applications, powered by the OpenID Connect standard.
Overview
The Modstack Identity and User Management module is a turnkey solution that provides enterprise-grade authentication and user management. Based on the industry-standard OpenID Connect (OIDC) protocol, it is the recommended way to add authentication to any application built on ModStack.
Under the hood, the module is powered by Keycloak, an open-source, OIDC-certified identity provider and one of the most widely adopted solutions for deploying OIDC endpoints in production. By building on Keycloak, the module benefits from a mature, battle-tested codebase with an active open-source community — while ModStack handles the setup, configuration, and operational overhead for you.
The module provides a simple, one-click setup within your own account — no third-party SaaS dependency required. It can serve as the authentication backend for web applications, single-page applications (SPAs), or mobile applications. During installation, you specify a PostgreSQL database that will be used to store all authentication and user data.
The module handles all standard authentication flows out of the box: registration, login, logout, password reset, two-factor authentication, and more.
Why use this module?
When adding authentication to an application, you typically have two choices: build the flows yourself, or use an off-the-shelf solution.
Building authentication from scratch requires a considerable investment of engineering time to correctly implement registration, login, token management, password resets, multi-factor authentication, and session handling. Given this complexity, it is now standard practice to use a proven OIDC solution rather than building all of these scenarios yourself. You could choose a commercial SaaS solution such as Auth0, but these come with significant account limits and feature gates that quickly become expensive as you scale.
How ModStack is different
Simple setup
Deploy a full authentication backend with a single click — no need for an in-depth understanding of OIDC. The module handles token issuance, session management, and protocol compliance for you.
Multi-tenant by default
Tenants are a first-class construct — not an add-on or a premium feature. Create and manage an unlimited number of tenants through the UI or the API, with full user isolation.
No usage limits or feature gates
No caps on users, tenants, or organizations. You do not pay per monthly active user. MFA, recovery codes, invite-only registration, brute force detection, audit logs, and tenant management are all included.
No third-party SaaS dependency
Runs entirely within your own infrastructure. Your authentication data never leaves your environment, and you are not dependent on the availability or pricing changes of an external vendor.
Innovative features
Capabilities you won't find in off-the-shelf solutions. Invite-only registration mode lets you control exactly who can sign up to your service using single-use or bulk invite codes — ideal for early-access launches and controlled rollouts.
Reduced lock-in
Built on the open-source, OIDC-certified Keycloak project. Export your database and import it into a standalone Keycloak deployment at any time — users, tenants, credentials, and configuration all transfer cleanly.
Features
Registration & Authentication
The module handles the full user authentication lifecycle out of the box:
- User registration — a standard registration screen for new users. The default fields are email and password; optionally, you can enable first name and last name. All screens can be customized with your own HTML templates (see UI Customization).
- Login — a standard login screen that authenticates users and redirects them to the home URL configured in the OIDC application. If two-factor authentication is enabled, users are prompted for their one-time password after completing the login.
- Password recovery — if a user has forgotten their password, they can use the password recovery screen to request a reset link.
- Logout — standard logout with configurable post-logout redirect URLs.
Registration Styles
The module supports two registration styles:
Open registration
This is the default. Any user who has access to the registration URL can sign up for an account.
Invite-only registration
In this mode, users need a valid invite to register. This is typically used when launching a new product and you want to control who has access to the system.
Invites can be generated in two ways:
- Single-user invite — creates an invite tied to a specific email address. Only that user can register with the invite link, and the link can only be used once.
- Bulk invite — creates a shared invite link that allows a configurable number of users to register. For example, you can create a bulk invite allowing up to 10 users to register and share the link with a client.
Both types of invites can be generated from the ModStack Runtime API (see Registration Invites).
The default registration style is open, but this can be changed at any time from the module Configuration UI.
Multi-Tenancy & User Management
The system is multi-tenant by default. Each account created on the system is an independent tenant with its own isolated user pool. The owner of a tenant can invite other users to join the tenant — refer to the Runtime API for details on creating tenant invites.
- Tenant isolation — each tenant has its own user pool, ensuring complete data separation.
- User administration — create, update, disable, and delete users through the Runtime API.
- Invite flows — invite users to a tenant via email, with configurable onboarding workflows.
- Tenant-scoped tokens — issued tokens include the tenant context, making it easy for your services to enforce tenant boundaries.
Two-Factor Authentication
Two-factor authentication (2FA) adds an additional layer of security by requiring a one-time code from an authenticator app during login. By default, 2FA is not enabled.
You can configure 2FA in two ways:
- Mandatory during registration — if you want every user to set up 2FA when they create their account, enable the Require 2FA during registration option in the Configuration UI.
- Per-user, on demand — if you want specific users to enable or disable 2FA dynamically, use the ModStack Runtime API (see User Management). When 2FA is enabled for a user who hasn't set it up yet, they will be prompted to configure it on their next login.
Recovery Codes
Recovery codes allow users to log into the system if they have lost their two-factor authentication device. Once logged in with a recovery code, the user can reset their 2FA to set up a new device.
By default, recovery codes are not enabled. You can configure them in two ways:
- Mandatory during registration — toggle the Enable recovery codes during registration option in the Configuration UI to require all users to save recovery codes when they first register.
- Per-user, on demand — use the ModStack Runtime API to enable or disable recovery codes for individual users.
Password Strength & History
Configure the minimum password requirements for your users, including minimum length and the required number of lowercase and uppercase characters. By default, the minimum password length is 8 characters and at least one uppercase character is required.
You can also enforce a password history policy to prevent users from reusing recent passwords. For example, you can configure the system to reject any of a user's previous 10 passwords, ensuring that password rotations are meaningful.
All of these requirements are set in the Password strength section of the Configuration UI.
Brute Force Attack Detection
In a brute force attack, an attacker points an automated script at your login page and hammers it with password guesses — either working through a dictionary against one account, or trying leaked passwords across many accounts. Left unchecked, a bot can attempt thousands of guesses per minute. The module detects consecutive login failures per account and responds with two complementary defenses:
- Quick-retry throttling — real users take seconds to retype a password; bots retry within milliseconds. Successive attempts that land inside the configured quick-retry window (default 1 second) are identified as automated, and the module enforces a minimum wait (default 5 seconds) before the next attempt is accepted. This collapses a bot's guess rate from thousands per minute to a handful, while a human typing at normal speed never notices it.
- User lockout — throttling slows an attacker down but doesn't stop a patient one. After a configurable number of consecutive failures (default 3), the account is locked so the guessing cannot continue at any speed. A locked account can be unlocked by an administrator through the Runtime API.
Both defenses are enabled by default and work together: throttling absorbs fast automated bursts without inconveniencing real users who mistype once or twice, while lockout caps the total number of guesses an attacker can ever make against an account. The detection switch, failure threshold, quick-retry window, and enforced wait are all configurable in the Brute force attack detection section of the Configuration UI.
Audit Logs
The module maintains a log of user actions over a configurable period of time. Logged events include login, logout, session expiry, password reset, and other authentication-related actions.
UI Customization
All user-facing screens — registration, login, password reset, and email templates — can be fully customized by providing your own HTML templates. This allows you to match the authentication experience to your application's branding and design language. Keycloak uses FreeMarker templates (.ftl) for the UI. You can learn more about Keycloak UI templating in the Keycloak Theme Developer Guide.
The default Keycloak theme used by the Modstack Identity and User Management module is open-sourced and available at github.com/Modstack-dev/modstackpublic/tree/main/keycloak-theme. You can create a new theme based on these files by modifying the styles, colors, fonts, and layout to match your application.
You can also use an AI assistant to generate a custom theme for you. Use the following prompt with your preferred model:
Configuration UI
The module administration UI is available in the ModStack console. Use it to install the module and configure system-level features that apply to the entire deployment — registration style (open vs invite-only), mandatory two-factor authentication, password strength requirements, brute force detection thresholds, and OIDC clients. To configure specific tenants or users, use the Runtime API.
The Configure screen is organized into six tabs, each containing one or more sections. Every section has an Update button that applies its changes immediately; changes are executed as a background task and the result is reported inline. The field labels below match the console exactly, so this page doubles as a reference for the in-UI info icons.
Authentication
Control how users sign in to your application — login and session behavior, password strength requirements, brute-force protection, and which events are recorded to the audit log.
Authentication details
Tunes the core behavior of the registration and login flows — what users must provide and verify when they create an account, and how the realm presents itself.
| Field | Type | Default | Description |
|---|---|---|---|
| Require email verification | Toggle | Off | Require users to verify their email when they create an account. When enabled, an activation email is sent to the user's address and the account activates only after they follow the link. Requires SMTP: the SMTP settings must be populated first — verification emails cannot be sent without a configured mail server. |
| Require Two Factor setup during signup | Toggle | Off | Require users to set up two-factor authentication when they create an account. When disabled, users can still enable 2FA themselves after logging in. |
| Require Two Factor recovery codes setup during signup | Toggle | Off | Require users to configure 2FA recovery codes when they create an account, so they can recover access if they lose their authenticator device. |
| Require First Name and Last Name | Toggle | Off | Adds mandatory first-name and last-name fields to the registration page. |
| Realm display name | Text (1–80 chars) | — | The display name of the authentication realm. Set this to your company name — it is shown, for example, when users add the account to their authenticator app. |
Password strength
Sets the complexity policy that user passwords must meet, and how many previous passwords are remembered to prevent reuse. See the Password Strength & History feature overview for how this is enforced.
| Field | Type | Range | Description |
|---|---|---|---|
| Minimum length | Integer | 8–50 | Minimum length of user passwords. |
| Minimum number of upper case characters | Integer | 1–10 | Minimum number of upper-case characters required in user passwords. |
| Minimum number of special characters | Integer | 1–10 | Minimum number of special characters required in user passwords. |
| Password history to retain to avoid reuse of passwords | Integer | 10–100 | Number of previous password hashes retained per user — passwords are never stored in plaintext. A new password is rejected if it matches any of the retained hashes. |
Audit log settings
Controls which authentication events are recorded and how long they are retained. The recorded events are viewable on the Audit logs tab.
| Field | Type | Default | Description |
|---|---|---|---|
| Enable user events | Toggle | On | Log user events — login, logout, password change, and similar. |
| User event expiration (secs) | Integer (3,600–2,592,000) | 604,800 (7 days) | User events are kept until this many seconds have elapsed. |
| Enable admin events | Toggle | Off | Log administrative events — realm and client configuration changes. |
| Show detail in the admin events | Toggle | Off | Include the full change payload in recorded admin events. |
| Admin event expiration (secs) | Integer (3,600–2,592,000) | 604,800 (7 days) | Admin events are kept until this many seconds have elapsed. |
Brute force attack detection
Detects repeated failed logins and slows down or locks out the offending account, protecting your users against password-guessing bots. See the Brute Force Attack Detection feature overview.
| Field | Type | Default | Description |
|---|---|---|---|
| Enable Brute force attack detection | Toggle | On | Master switch for brute-force detection. |
| Lock user on multiple login failures | Toggle | On | Lock the user account when repeated login failures are detected. Locked users can be unlocked via the Runtime API. |
| Number of login failures after which to lock account | Integer (3–10) | 3 | The account is locked after this many successive failures. |
| Quick retry window (milliseconds) | Integer (500–5,000) | 1,000 | Time window in which repeated login attempts are identified as a “quick” retry. Quick retries are throttled to stop rapid automated attempts. |
| Minimum wait after a quick retry (secs) | Integer (5–120) | 5 | The minimum delay enforced before another login attempt is allowed after a quick retry. |
OAuth clients
Manage the OAuth/OIDC clients that applications use to authenticate against this module. Create one client for each of your applications that integrates with the auth infrastructure — for example one for your web app, one for your CLI, and one for your mobile app. Clients can be added, updated, and deleted from this tab.
What is an OAuth client?
An OAuth client is the registered identity of one of your applications inside the authentication system. When your application sends a user off to log in, it identifies itself with its client ID; after the user authenticates, the module issues tokens to that client and will only redirect the user back to the URLs registered for it. The client therefore defines the contract between your application and the auth infrastructure: what kind of application it is, where users may be sent after login and logout, and which browser origins may call the auth server.
Registering one client per application keeps those contracts separate — your web app, CLI, and mobile app each get their own client ID, their own allowed URLs, and their own login/registration links, so a change (or a compromise) in one application never affects the others.
Client types
To integrate with the Modstack Identity and User Management module, you need to create one or more OIDC client applications. The module supports two types of clients:
Private clients are used with the Authorization Code flow (also called the Standard flow in Keycloak). These clients are capable of securely storing a client secret and are used for standard server-side web applications.
Public clients are used for applications that cannot securely store a secret — typically CLI tools, mobile applications, or single-page applications (SPAs). For SPAs, the recommended flow is the Standard flow with PKCE enabled. For CLI applications, the recommended flow is the Device Grant flow.
| Flow | Client Type | Use Case |
|---|---|---|
| Standard flow (Authorization Code) | Private | Server-side web applications |
| Standard flow with PKCE | Public | SPAs and mobile applications |
| Device Grant | Public | CLI applications |
Client fields
Each client entry carries the following fields. The client type and ID identify the client and cannot be changed after creation; everything else can be updated at any time.
| Field | Type | Description |
|---|---|---|
| OAuth client type | Drop-down | One of PrivateStandard, PublicStandardPKCE, or PublicDeviceGrant (see the flow table above). Cannot be changed after creation. |
| Client ID | Text (1–80 chars, alphanumeric and dashes) | The name used as the ID of your application. Cannot be changed after creation. |
| Description | Text (1–80 chars) | A human-readable description of the client. |
| Home page URL | URL | The main URL of your application. |
| Web origins | URL list (1–25 entries) | Allowed CORS origins for browser-based requests. These origins are permitted to make direct token and API requests to the Keycloak server from client-side JavaScript. Use * to generalize. |
| Valid redirect URLs | URL list (1–25 entries) | The list of valid URLs that the user can be redirected to after a successful login. Use * to generalize. |
| Post logout URLs | URL list (1–25 entries) | The list of valid URLs that the user can be redirected to after logging out. You can specify a particular URL as part of the logout link to control where the user lands after signing out. Use * to generalize. |
Login URLs
Every client row has a Show Login URLs panel with ready-to-use, copyable OIDC URLs for that client:
| URL | Description |
|---|---|
| Registration URL | Direct link your application can use to send users to the registration page for this client. |
| Login URL | Direct link your application can use to send users to the login page for this client. |
| Logout URL | Direct link your application can use to log users out of this client. |
User Registration
Configure how new users join — self-registration or invite-only — along with email verification and related sign-up behavior.
Registration Style
Chooses the type of registration for your application. With Open registration, any user with the registration URL can sign up. With InviteOnly, only users holding a valid invite link can register — see the Registration Styles feature overview and the Runtime API for generating invites. The mode can be changed at any time and applies immediately.
| Field | Type | Default | Description |
|---|---|---|---|
| Select Registration Style | Drop-down | Open | Open or InviteOnly. A common pattern is to start with InviteOnly during a private launch and switch to Open when you are ready. |
SMTP
Configure the outgoing mail server used to send emails such as verification, password reset, and invitation messages.
SMTP settings
Connection and identity settings for the outgoing email server. This section also carries a Reset action that clears the stored SMTP configuration and returns the section to its defaults.
| Field | Type | Default | Description |
|---|---|---|---|
| From Email Address | — | The From address for any emails sent out by the module. | |
| From Address display name | Text (1–255 chars) | — | The display name shown alongside the From address. |
| SMTP host | Hostname | — | Hostname of your SMTP server, e.g. smtp.sendgrid.net. |
| SMTP port | Integer (1–65,536) | 587 | Usually 587 for STARTTLS or 465 for implicit TLS. |
| Auth required | Toggle | On | Whether the SMTP server requires authentication. |
| UserName | Text (1–255 chars) | — | Username used to authenticate against the SMTP server. |
| SMTP password | Secret | — | Password or API key used to authenticate against the SMTP server. Stored securely and never displayed back in the UI. |
| SSL | Toggle | Off | Whether SSL is required to connect to the SMTP server. |
| StartTLS | Toggle | On | Whether StartTLS is required to connect to the SMTP server. |
IDP integration
Connect external identity providers such as GitHub and Google so users can sign in with their existing accounts. The tab contains one section per provider — GitHub integration and Google integration — each with the same two fields. Once configured, a “Continue with…” button appears on the login page.
GitHub / Google integration
Allows your users to log in with their GitHub or Google credentials. Create an OAuth App with the provider first, then paste its credentials here. Both fields are optional — leaving them empty disables that provider.
| Field | Type | Description |
|---|---|---|
| OAuth clientId | Text (max 255 chars) | The clientId of the OAuth App created with the provider for this integration. |
| OAuth client Secret | Secret (32–512 chars) | The secret of the OAuth App created with the provider. Stored securely and never displayed back in the UI. |
Audit logs
Review recent authentication events, logins, failures, and administrative changes for the past 7 days (or your configured retention). Which events are recorded — and for how long — is controlled by the Audit log settings section on the Authentication tab.
The log is a read-only table, paged at 15 entries, with a Download CSV action that exports the full log. Each entry carries:
| Column | Description |
|---|---|
| Time | When the event occurred. |
| Event type | The kind of event — e.g. login, logout, password change, or an admin configuration change. |
| User | The user the event applies to. |
| Client | The OAuth client through which the event happened. |
| IP address | Source IP address of the request. |
| Details | Additional event-specific detail. |
Runtime API
The Runtime API provides programmatic access to manage users, tenants, invites, and authentication settings at runtime. It is a REST API accessible to other services in the same environment and can be consumed from a client in any language. Authentication for the API is based on client credentials. Refer to the Calling the Runtime API section to understand how to make these calls.
Building the Invite Link
Registration invites
The create invite calls (bulk & single) return an inviteId. To build the registration invite link, append the invite ID as a query parameter to the registration page URL of your OIDC application using the parameter registrationInviteId.
https://<your-auth-domain>/realms/app/protocol/openid-connect/registrations?client_id=<client-id>®istrationInviteId=<invite-id>
Tenant invites
If you are inviting users to a tenant using the inviteusertotenant call, the API call also returns an invite ID. To build the registration invite link, append the invite ID as a query parameter to the registration page URL of your OIDC application using the parameter tenantInviteId.
https://<your-auth-domain>/realms/app/protocol/openid-connect/registrations?client_id=<client-id>&tenantInviteId=<invite-id>
Calling the Runtime API
The Runtime API is accessible to any service running in the same environment. All requests must be authenticated using client credentials — obtain an access token from the OIDC token endpoint using your client ID and secret, then pass it as a Bearer token in the Authorization header. The client ID used for Runtime API calls is modstackapp. The credentials for this client ID can be accessed from the Configuration UI.
The Runtime API is only accessible inside the environment by connecting to the modstackauth-authhelper service on port 80. This service is not exposed externally via ingress.
Calling the API is a two-step flow:
- Obtain an access token — POST your client credentials (
client_idandclient_secret) to the token endpoint of theservicerealm using the OAuthclient_credentialsgrant. The endpoint replies with JSON of the form{"access_token": "eyJh…", "expires_in": 300, "token_type": "Bearer"}— extract theaccess_tokenfield. Tokens are short-lived: reuse the same token for subsequent calls and request a new one whenexpires_in(seconds) elapses or a call answers401. - Call the Runtime API — send the token in the
Authorization: Bearerheader. The example below callsGET /v1.0/userstore/tenant, which lists all the tenants in the user store — every other Runtime API endpoint is called the same way.
# POST the client credentials to the token endpoint. The JSON
# response carries the token in access_token — extracted here
# with jq and kept in a shell variable for the next call.
ACCESS_TOKEN=$(curl -s -X POST https://<your-auth-domain>/realms/service/protocol/openid-connect/token \
-d "grant_type=client_credentials" \
-d "client_id=modstackapp" \
-d "client_secret=<client-secret>" | jq -r '.access_token')
# Lists all the tenants in the user store.
curl -X GET http://modstackauth-authhelper/v1.0/userstore/tenant \
-H "Authorization: Bearer $ACCESS_TOKEN"
// POST the client credentials to the token endpoint.
const tokenRes = await fetch(
"https://<your-auth-domain>/realms/service/protocol/openid-connect/token",
{
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "client_credentials",
client_id: "modstackapp",
client_secret: "<client-secret>",
}),
},
);
// The JSON response carries the token in access_token.
const { access_token } = await tokenRes.json();
// Lists all the tenants in the user store.
const res = await fetch(
"http://modstackauth-authhelper/v1.0/userstore/tenant",
{ headers: { Authorization: `Bearer ${access_token}` } },
);
const tenants = await res.json();
import requests
# POST the client credentials to the token endpoint.
token_res = requests.post(
"https://<your-auth-domain>/realms/service/protocol/openid-connect/token",
data={
"grant_type": "client_credentials",
"client_id": "modstackapp",
"client_secret": "<client-secret>",
},
)
# The JSON response carries the token in access_token.
access_token = token_res.json()["access_token"]
# Lists all the tenants in the user store.
res = requests.get(
"http://modstackauth-authhelper/v1.0/userstore/tenant",
headers={"Authorization": f"Bearer {access_token}"},
)
tenants = res.json()
HttpClient client = HttpClient.newHttpClient();
//POST the client credentials to the token endpoint.
String form = "grant_type=client_credentials"
+ "&client_id=modstackapp"
+ "&client_secret=" + URLEncoder.encode("<client-secret>", StandardCharsets.UTF_8);
HttpRequest tokenRequest = HttpRequest.newBuilder()
.uri(URI.create("https://<your-auth-domain>/realms/service/protocol/openid-connect/token"))
.header("Content-Type", "application/x-www-form-urlencoded")
.POST(HttpRequest.BodyPublishers.ofString(form))
.build();
String tokenJson = client.send(tokenRequest, HttpResponse.BodyHandlers.ofString()).body();
//The JSON response carries the token in access_token — extract it
//with your JSON library, e.g. Jackson:
String accessToken = new ObjectMapper().readTree(tokenJson).get("access_token").asText();
//Lists all the tenants in the user store.
HttpRequest apiRequest = HttpRequest.newBuilder()
.uri(URI.create("http://modstackauth-authhelper/v1.0/userstore/tenant"))
.header("Authorization", "Bearer " + accessToken)
.GET()
.build();
String tenants = client.send(apiRequest, HttpResponse.BodyHandlers.ofString()).body();