How to connect your application with the Vipassana Identity Server
- Benefits of using VIS
- What VIS does NOT handle
- Using the staging server
- Controlling visibility in the My Apps gallery
- Delegating the authentication to VIS
- Using OpenID Connect
- Handling OAuth state
- Handling user sessions
- Handling VIS 5xx errors
- Migrating existing user accounts to VIS
- Defining the user registration flow
- Handling user account updates and deletions
- Propagating updates from your application to VIS
- Handling user merges
- Integrating the VIS User Credentials Page
- Requiring MFA
You can delegate the authentication of your application to Vipassana Identity Server (VIS), so that you don't have to manage passwords, account recovery, profile update, etc.
When a user logs into your application, they are redirected to VIS for authentication. VIS then returns proof of successful authentication, along with the user's data.
To integrate VIS, follow these steps:
- contact [email protected] or [email protected] to obtain OAuth credentials for your application and access your application OAuth configuration in the VIS UI
- update your codebase to delegate authentication to VIS
- migrate the existing user accounts of your application to VIS
- define the user registration flow
- handle data synchronization between your application and VIS
Benefits of using VIS
- Users with a VIS account don’t need new credentials to access your application
- VIS handles authentication security
-
You can customize the authentication experience in several ways, such as:
- require a multi-factor authentication
- display custom messages on the VIS login page
- allow authentication through Office 365, GMail or an Apple account
- etc. See the VIS-specific OAuth Parameters documentation for all options
What VIS does NOT handle
VIS stores only generic user attributes, see the list here. Your application must store additional attributes in its own database. Your application can also store and duplicate these generic attributes in its own database, but in that case it must handle the syncing of these attributes with VIS (see subsequent sections).
The VIS UI allows users to update only their email, username, and password.
All the other generic attributes are managed by the applications connected to VIS.
When possible, it is better to let the applications handling student registration (myCourses, dhamma.org) manage these other generic attributes.
A user’s preferred registration application depends on their primary_country attribute.
Each VIS user has a UUID. Your application must store it to map its local user records to VIS users.
VIS doesn’t handle authorization. It only confirms whether a user is authenticated.
Using the staging server
Use the VIS staging website to test changes before switching your production application to VIS.
To prevent accidental communication the staging server does not deliver emails triggered by API endpoints.
The staging server delivers emails triggered by end user:
- password reset email: can be sent from https://test.identity.dhamma.org/en/users/password/new
- invitation email: can be resent from https://test.identity.dhamma.org/en/help after entering the email. If the user has a pending invitation, they can click the "Resend invitation instructions" button. Resends are rate limited, so wait a minute if repeated attempts stop working.
- confirmation email: can be resent from https://test.identity.dhamma.org/en/users/confirmation/new
- account merging confirmation email
Controlling visibility in the My Apps gallery
Each OAuth application has a Show in gallery checkbox (enabled by default) that controls whether it appears in the user-facing "My Apps" section of the VIS home page. Uncheck it for desktop apps, CLI tools, mobile-only apps and service accounts that have no web launch URL. SSO continues to work for hidden applications.
Delegating the authentication to VIS
Use the OAuth 2 Authorization Code Flow (with or without OpenID Connect — see Using OpenID Connect).
First your application should redirect users to the VIS authorization endpoint https://identity.dhamma.org/oauth/authorize.
Here is an URL example:
https://identity.dhamma.org/oauth/authorize?client_id=XXX&locale=en&redirect_uri=YYY&response_type=code&scope=default&state=ZZZ
Major programming languages have online implementation guides and open-source OAuth 2 client librairies (see OAuth librairies). AI assistants can also help implement the OAuth 2 Authorization Code Flow.
At the end of this OAuth flow your application POSTs to the VIS /oauth/token endpoint and receives a JWT token in the JSON response body access_token key.
Your application can decode this token to get the user data. Here is how the omniauth_vis gem handles for ruby applications.
Alternatively your application can request /api/v1/me.json to get the user data.
VIS issues expiring access tokens. The token carries iss, iat, exp, aud (your application's client id) and jti,
plus a user object and an application object.
exp matches the lifetime VIS itself enforces, so once it has passed the token is no longer accepted and your application must obtain a new one.
Always verify the token with the algorithm pinned.
The access token is signed with HS512 using your application's jwt_secret; a decoder that accepts whatever
alg the token header asks for can be handed a token signed with none, or with an algorithm you did not intend.
With the ruby jwt gem that means JWT.decode(token, jwt_secret, true, algorithm: "HS512") — never
JWT.decode(token, nil, false). Verify aud against your own client id as well, so a token minted for
another application is rejected.
If you request scope=openid instead of scope=default, the token response also includes an id_token — see Using OpenID Connect.
The authorization-code token response includes refresh_token only when your application
has opted in through its VIS configuration. To renew access, POST to /oauth/token with
grant_type=refresh_token, the current refresh_token, and your client credentials.
Save the new refresh token returned by each successful exchange: the previous value is immediately
revoked, and replay revokes the active authorization lineage. Refresh must happen within the configured
inactivity lifetime and the absolute lifetime since the original authorization. Rotation resets only
inactivity. On invalid_grant, start a new authorization flow. Refresh is disabled by default
and is never issued for client_credentials; offline_access is not supported.
Using OpenID Connect
VIS fully supports OpenID Connect on top of the standard OAuth 2 Authorization Code Flow.
To use it, request scope=openid in your authorize request:
https://identity.dhamma.org/oauth/authorize?client_id=XXX&locale=en&redirect_uri=YYY&response_type=code&scope=openid&state=ZZZ
The token response will include an id_token alongside the access_token.
The ID token is a signed JWT (RS256) containing standard OIDC claims (iss, sub, aud, exp, iat),
nonce (when the client sends a nonce parameter), auth_time (when the user has authenticated),
plus sid, amr, acr, email, username, and full_name.
The ID token is signed with the VIS OIDC key (separate from the app-specific jwt_secret used for access tokens).
VIS publishes a discovery document at /.well-known/openid-configuration and a JWKS endpoint at /oauth/discovery/keys.
The UserInfo endpoint at /oauth/userinfo returns the same user data claims as in the ID token.
The access token remains a JWT (HS512, signed with your application's jwt_secret, carrying its own exp and aud) regardless of the scope — requesting scope=openid adds the ID token but does not change the access token format.
Handling OAuth state
Your application owns the OAuth state value. VIS only receives it in the authorization request and returns it unchanged when redirecting back to your redirect_uri.
Your application must generate a unique, unguessable state value for each OAuth attempt, store it temporarily on your side, and verify it when VIS redirects back.
Treat state as a short-lived, single-use value. Once your application receives a callback from VIS, it should first verify that the returned state matches the value it stored, then delete it, before exchanging the authorization code for a token.
If the callback contains a state value that your application no longer knows, do not try to recover that OAuth attempt from VIS. Reject that callback, clear any local partial login state, and start a fresh OAuth authorization request if the user still needs to sign in.
This is important because users can start more than one OAuth flow in the same browser, use the Back button, retry after a timeout, or return to a tab whose local application state has already expired.
In these cases VIS may still be able to complete a browser redirect, but only your application can know whether the returned state still belongs to an active login attempt.
Internally, VIS stores the current authorization request URL, including the state query parameter, in an encrypted browser session cookie while the user is completing VIS-side steps such as sign-in, password confirmation, TOTP verification, account completion, or password reset.
VIS does not store this state as durable server-side application state and it is not a source of truth for your application.
VIS keeps this browser-side authorization request until one of the following happens:
- VIS completes the authorization flow and redirects back to your application
- the same browser starts a new VIS OAuth authorization request, replacing the previous one
- the user signs out of VIS, which clears OAuth-related cookies and session values
- the browser drops its session cookies
Because the VIS continuation cookie is a browser session cookie, it has no explicit application-defined duration. Some browsers may also restore session cookies when reopening tabs.
Therefore your application should enforce its own expiration for pending OAuth state values rather than relying on VIS or the browser to expire them at a specific time.
Handling user sessions
Your application keeps managing the user sessions, including their duration.
When a user logs out from your application, they are not automatically logged out of their VIS session. To ensure their VIS session ends as well, redirect them to the VIS Sign Out URL: https://identity.dhamma.org/en/users/sign_out.
You can include an optional redirect_to query param to send the user to a specific URL after the VIS sign out. For security, that URL must belong to your application — its host must match one of your registered redirect URIs or your application home page; otherwise the user is returned to the VIS home page.
Receiving back-channel logout notifications
To end application sessions when a VIS session is revoked, configure a public HTTPS Back-channel logout URL in your OAuth application settings.
VIS asynchronously POSTs a form-encoded logout_token when the user signs out or a user or administrator revokes a VIS session, provided that session has a live access token for your application. Account-wide security events notify every application for which the user has a live access token. Return a 2xx response promptly. Delivery is best-effort and may be retried, so handle notifications idempotently and retain your own session expiry.
The logout token is an RS256 JWT signed with the VIS OIDC key published at /oauth/discovery/keys; its header carries typ=logout+jwt and the matching discovery kid. Validate its signature, iss, aud (your client ID), iat, exp (120 seconds after iat), unique jti, and back-channel logout event. Also verify that it has sub, sid, or both and has no nonce. Section 2.6 permits additionally checking any sid against current or recent ID tokens.
VIS session events contain both sub and sid. The sid is the same opaque value carried by the ID token, so store it with the application session. When sid is present, end only the local session or sessions stored under that issuer and sid. An unknown sid must not fall back to ending every session for sub; treat it as an already-ended session. When sid is absent, the event is account-wide and you must end all local sessions for the user identified by iss and sub.
Duration of a VIS session
- 3.0 hours
- 2 months when they checked "Remember Me" on the login form.
Any time a user interacts with the VIS website, their session duration is automatically extended by 3 hours.
When the user session has expired in your applicaiton, you can request the user to re-authenticate in VIS by setting confirm_identity to true (see the VIS-specific OAuth Parameters documentation):
even if the VIS user session is still active, the user will have to re-enter their. password.
Handling VIS 5xx errors
The VIS servers may occasionally return 5xx (server error) responses under load or during maintenance. Your application must handle these errors gracefully for all HTTP requests it makes to VIS:
-
OAuth token exchange — if the POST to
/oauth/tokenfails with a 5xx, do not treat the user as authenticated. Retry the request with exponential backoff and a reasonable maximum number of attempts. After all attempts are exhausted, show the user an error page asking them to try again later. - VIS API calls — if any API request (create, update, delete, user-info, invite, etc.) returns a 5xx, retry with exponential backoff. If the error persists, log the failure and alert your operations team rather than silently dropping the operation.
Use standard HTTP client libraries that support retry with exponential backoff and jitter. Set timeouts short enough to detect failures quickly but long enough to avoid false positives under normal conditions.
Migrating existing user accounts to VIS
You can bulk-create user records in VIS through the bulk_create API endpoint.
Note that users will be emailed to confirm their email address unless you set the API parameter email_confirmed to true.
Defining the user registration flow
You can let users self-register in VIS when they authenticate for the first time. For this, set the allow_sign_up to true (see the VIS-specific OAuth Parameters documentation).
They will create their VIS account on the VIS Sign Up page.
To send them to your own sign-up page instead (for example CALM), set custom_sign_up_url.
Once the user is redirected back to your application, it is up to you to create a user record in your application's database and implement further registration steps if needed.
If self-registration doesn’t suit your application, you can implement the user creation in your application codebase. Then use the API Invite endpoint to let the user complete their registration on VIS. This endpoint creates a User record in the VIS database and sends a confirmation to the user, unless the user already has a VIS account. The endpoint returns the VIS user payload to your application, including the VIS user identifier.
Handling in your application the user account updates and deletions which happen in VIS
User Attribute Updates
VIS user attributes can be updated in VIS by the users themselves or by another application. For example, a user can change their email in the VIS UI, or their family name in another application. The email, username and password are owned by VIS and cannot be updated by your application.
After authenticating on VIS, the user is redirected to your application with their attributes in the payload. It is up to your application to compare these attributes with any values stored in its database and update them accordingly.
Sometimes it is better not to wait for the next user authentication to update the attributes stored in your application. For example, if a user updates their email, you may want your application to update the email as soon as it is changed in VIS. See the Webhooks doc to achieve that.
In any case, when your application applies locally updates coming from VIS, make sure your application does not propagate these changes back to VIS.
User Deletions
A VIS user record can be deleted permanently, e.g. on a privacy request. Your application will know about this deletion only if you enable a deletion webhook. See the Webhooks doc.
Propagating User Updates or Deletions from Your Application to VIS
Use the VIS API.
Make sure your application does not propagate user updates to VIS when these updates originally came from VIS.
When you send a user deletion API request, the VIS user record is deleted only if it’s linked exclusively to your application. If the user is mapped to other applications, only the VIS mapping between the user record and your application will be deleted.
Handling User Merges
A user may have multiple VIS user accounts. Each time a user logs into VIS, VIS searches for other VIS user accounts that may belong to the same user. This search compares email addresses, names, birth dates, and other attributes.
If potential duplicates are found, VIS offers the user the option to merge these accounts into a single one. For the merge to proceed, the user must prove they own these accounts through an email challenge. The user can also choose to not merge, and VIS will remember this choice.
Once the merge is completed, the merged accounts are marked as inactive in VIS, and the user is left with a single active account.
VIS notifies your application when such merges happen if you enable the "user merge" webhook. See the Webhooks doc.
Conversely, if your application merges user records and these records are mapped to VIS user records, you should use the Delete User API endpoint to remove the merged records from VIS.
Integrating the VIS User Credentials Page in Your Application
The VIS UI includes a Credentials page where users can update their email and password.
To allow embedding, enable Allow iframe embedding on your OAuth application in VIS.
VIS will trust the origin of the application Home page URL.
You can integrate the Credentials page directly in your application through an iframe:
<iframe scrolling="no" src="https://identity.dhamma.org/en/current_user/credentials?iframe=true" name="identity-server-iframe" style="height: 200px;"></iframe>
along with this JavaScript code:
// Listen to some message sent by child iframe (used by VIS)
window.addEventListener('message', (message) => {
if (message.data.event == 'height') {
const event = message.data
const selector = event.iframeName ? `iframe[name="${event.iframeName}"]` : 'iframe'
const iframe = document.querySelector(selector)
iframe.style.height = `${event.value}px`
}
// Some links do not work inside iframe (for external SSO providers).
// The redirection should happen in the main window.
if (message.data.event == 'redirect') {
window.location.href = message.data.href
}
})
Requiring MFA
In addition to a password, you can request multi-factor authentication (MFA). At the time of writing VIS supports only Time-based one-time password (TOTP) for MFA.
To require a MFA verification, set the VIS OAuth parameter require_mfa to true in your OAuth Authorize request.
If your application requires MFA for only certain users or actions:
- Your application first sends a standard OAuth Authorize request to VIS in order to authenticate the user
- Once back in your application, if MFA is needed, send a second OAuth Authorize request with
require_mfatotrue(see the VIS-specific OAuth Parameters documentation for additional MFA options) - If the user has not set TOTP in VIS yet, they are redirected first to the TOTP setup page
- If TOTP is already set up, they are asked to enter a 6-digit TOTP code, then redirected back to your application
- As usual, VIS returns user data, including
last_mfa_at, an ISO 8601 / RFC 3339 timestamp string (e.g., "2025-10-07T09:44:37.802Z")
When setting require_mfa VIS will always ask for a TOTP code, unless the user already provided a valid code in the last 30 seconds.
So it is up to your application to maintain a "MFA" session to know when to ask for MFA again.
You can also require users to set up TOTP gradually.
Use the require_mfa_from_date parameter in the VIS API Create and Update endpoints (users are automatically emailed when a date is set and reminded one week before the date).
- Before the set date, users are offered to setup TOTP (even if the OAuth parameter
require_mfais nottrue) but they can skip this setup (even ifrequire_mfaistrue) - Once the date is reached, TOTP setup becomes mandatory
This allows you to enforce MFA from a future date while giving users time to prepare.
Supporting MFA reset requests
The VIS help page invites users who need to reset their TOTP setup (e.g., if they lost access to their Authenticator app) to contact their application support person (e.g., the CALM Training and Support team).
You can share with your application support team the dedicated VIS support documentation.
You can implement TOTP reset for your support team in your application by using the Reset TOTP API endpoint.
While VIS admins and application configurators can reset the TOTP setup of users from the VIS UI, this is not something we encourage as a TOTP reset request should be dully verified, which is easier for application support teams.