This guide uses app.example.edu for the OmniPasskey management interface and idp.example.edu for the Shibboleth IdP. Replace those values with your production names before deployment.
Before you begin
database/, app/, packages/, and idp_plugin/.Installation order
- Initialize the shared passkey database.
- Install the OmniPasskey management interface using either the container or package path.
- Install and configure the OmniPasskey Shibboleth IdP plugin.
- Align the relying-party ID, frontend URL, inline-registration URL, and database connectivity across all components.
- Validate registration, credential management, and sign-in.
Step 1: Initialize the shared database
Choose the bootstrap SQL that matches your database backend:
- PostgreSQL:
database/postgresql/setup.sql - MySQL or MariaDB:
database/mysql/setup.sql
Create the database
- Replace every
<changeme>placeholder in the selectedsetup.sqlfile with strong passwords. - Connect as a database administrator.
- Load the bootstrap SQL so it can create the shared schema, runtime users, tables, and indexes.
psql -h db.example.edu -U postgres -d postgres \
-f database/postgresql/setup.sql
mysql -h db.example.edu -u root -p \
< database/mysql/setup.sql
Example shared backend settings
Both the management interface and the IdP plugin need matching backend details in their respective passkey-database.properties files.
passkey.db.backend=postgresql
passkey.db.name=shibboleth_passkey
passkey.db.validationQuery=select 1
passkey.db.postgresql.host=db.example.edu
passkey.db.postgresql.schema=shibboleth_passkey
passkey.db.postgresql.appUser=django
passkey.db.postgresql.appPassword=<app-password>
passkey.db.postgresql.idpUser=shib
passkey.db.postgresql.idpPassword=<idp-password>
passkey.db.backend=mysql
passkey.db.name=shibboleth_passkey
passkey.db.validationQuery=select 1
passkey.db.mysql.host=db.example.edu
passkey.db.mysql.appUser=django
passkey.db.mysql.appPassword=<app-password>
passkey.db.mysql.idpUser=shib
passkey.db.mysql.idpPassword=<idp-password>
Step 2: Install the management interface
Choose one deployment path for the customer-facing OmniPasskey management interface.
Container deployment path
Use the app/ directory when you want a full SP-facing OmniPasskey container image.
- Review
app/Dockerfile. - Add or replace your Shibboleth SP files under
app/container_files/shibboleth/, including metadata, keys, and certificates. - Update
app/container_files/httpd/ssl.confwith your external hostname and the runtime paths for the web-server TLS certificate and private key. - Keep the TLS private key outside the image and mount the certificate and key read-only at runtime. The shipped paths are
/etc/pki/tls/certs/localhost.crtand/etc/pki/tls/private/localhost.key. - Update
app/container_files/httpd/omnipasskey.conffor your real hostname and authorization rules. Set the HTTP redirect and HTTPSServerNameto the public application hostname, replace the deny-by-default admin and helpdesk rules with your Shibboleth user or entitlement rules, and allow/registration-server-backendonly from the IdP if inline registration is enabled. - Set the backend in
app/container_files/registration-server/passkey-database.properties, using the same backend type, database name/host, and matching application credentials as the IdP plugin’spasskey-database.properties. -
Create
app/container_files/registration-server/.envwith the deployment-specific values shown below.SERVER_NAME="Example University" ID=example.edu DJANGO_SECRET_KEY=<strong-random-secret> DJANGO_ALLOWED_HOSTS=localhost,app.example.edu DJANGO_DEBUG=falseIDis the WebAuthn relying-party ID. It is normally the registrable domain used by the application and must match the IdP plugin’s relying-party ID.DJANGO_SECRET_KEYis private signing material for Django sessions and other cryptographic operations; generate a strong, unique value and keep it out of source control.DJANGO_ALLOWED_HOSTSis the comma-separated list of hostnames Django may serve, and must include the management interface hostname. KeepDJANGO_DEBUG=falsein customer-facing deployments so Django does not expose debug details. - Optionally replace
app/container_files/www/logo.pngand addapp/container_files/www/theme.jsonfor site branding; see Branding for the supported files and display guidance.
docker build \
--build-arg REACT_APP_API_BASE=https://app.example.edu \
--build-arg REACT_APP_LOGOUT_URL=https://app.example.edu/Shibboleth.sso/Logout \
--build-arg SERVER_NAME="Example University" \
--build-arg SERVER_ID=example.edu \
-t omnipasskey-management \
app
docker run -d \
--name omnipasskey-management \
-p 80:80 \
-p 443:443 \
-e PASSKEY_SERVER_NAME="Example University" \
-e PASSKEY_SERVER_ID=example.edu \
-e DJANGO_SECRET_KEY=<strong-random-secret> \
--mount type=bind,src=/secure/app.example.edu.crt,dst=/etc/pki/tls/certs/localhost.crt,readonly \
--mount type=bind,src=/secure/app.example.edu.key,dst=/etc/pki/tls/private/localhost.key,readonly \
omnipasskey-management
The default logout target, /Shibboleth.sso/Logout, signs the user out of the local Shibboleth SP. If your institution uses a combined SP/IdP logout page, deploy that page separately and set REACT_APP_LOGOUT_URL to it. Adjust the TLS mount paths and ssl.conf together, and ensure Apache can read the mounted files.
The shipped Apache template keeps /passkey/admin, /passkey/helpdesk, the matching API paths, and /registration-server-backend denied until you replace those rules with your site-specific allow-lists.
Package deployment path
Use the native package path when you already operate an Apache and Shibboleth SP host.
Supported operating systems
| Package | Supported operating systems | Requirements |
|---|---|---|
| DEB | Debian 12 and 13 | Bookworm and Trixie |
| DEB | Ubuntu 22.04, 24.04, and 26.04 LTS | Use a fully updated LTS installation. |
| RPM | Rocky Linux 8, 9, and 10 | Keep Rocky Linux 8 and 9 fully updated so Python 3.12 packages are available. |
| RPM | RHEL 8, 9, and 10 | RHEL 8.10 or later and RHEL 9.4 or later are required. |
This includes ESM deployments. Its native Python and mod_wsgi stack uses Python 3.8, while Django 5.2 requires Python 3.10 or later.
What the host must already provide
The native package path assumes that the host already runs Apache and a configured Shibboleth SP. OmniPasskey adds its application routes to that environment; it does not create your site’s HTTPS virtual host or choose its Shibboleth entityID, metadata, keys, certificates, and handlers.
Install and configure the Shibboleth SP using the repository and operating procedures approved for your site. Before adding OmniPasskey, confirm that the SP can establish a session on the intended HTTPS host and that these host-owned Apache modules are enabled:
mod_sslfor the required HTTPS virtual host.mod_rewritefor management-app client-side routes.mod_shibfor Shibboleth session enforcement and attributes.
What the OmniPasskey package installs
The DEB and RPM declare their application runtime dependencies. Let apt or dnf resolve them when installing OmniPasskey; do not select a separate Python, WSGI, or cron package by hand.
- The DEB installs the distribution Python 3 runtime and
libapache2-mod-wsgi-py3. The RPM selects Python 3.12 and the native WSGI provider:python3.12-mod_wsgion EL 8 and 9 orpython3-mod_wsgion EL 10. - The DEB installs
cron; the RPM installscronie. - The post-install step creates the Django virtual environment, installs the bundled Python dependencies, registers the Django maintenance jobs in crontab, and attempts the initial metadata and icon download.
Debian or Ubuntu
apt install ./packages/shibboleth-passkey-app_*.deb
Rocky Linux or RHEL
dnf install ./packages/shibboleth-passkey-app-*.rpm
The Apache snippet is installed at /etc/apache2/conf-available/omnipasskey.conf on Debian/Ubuntu and /etc/httpd/conf.d/omnipasskey.conf on Rocky Linux/RHEL. DEB installation also runs a2enconf omnipasskey when the Apache helper is present.
After package installation:
- Edit the package-installed
omnipasskey.conffile in the native Apache configuration directory. Replace the deny-by-default admin and helpdesk rules with your Shibboleth user or entitlement rules. If inline registration is enabled, allow/registration-server-backendonly from the IdP host or network. -
Review
/var/www/html/passkey/env.js.window.__ENV__ = { API_BASE: "https://app.example.edu", LOGOUT_URL: "https://app.example.edu/Shibboleth.sso/Logout" }API_BASEis the browser’s base URL for the OmniPasskey API and application assets; set it to the user-facing HTTPS origin served by this installation.LOGOUT_URLis the Shibboleth SP logout endpoint, unless your institution uses a separately deployed combined SP/IdP logout page. -
Review
/var/www/registration-server/.env.SERVER_NAME="Example University" ID=example.edu DJANGO_SECRET_KEY=<strong-random-secret> DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1,app.example.edu DJANGO_DEBUG=falseIDis the WebAuthn relying-party ID and must match the IdP plugin configuration.DJANGO_SECRET_KEYis private signing material for Django sessions and other cryptographic operations; use a strong, unique value.DJANGO_ALLOWED_HOSTSis the comma-separated list of hostnames Django may serve and must include the management interface hostname. KeepDJANGO_DEBUG=falsein customer-facing deployments. - Set the backend in
/var/www/registration-server/passkey-database.properties. Selectpostgresqlormysql, then set the database host, name, schema where applicable, and application credentials. Use matching backend settings and IdP credentials in the IdP plugin’spasskey-database.properties. After changing these files, restart or reload the application and IdP services as appropriate. - Optionally replace
/var/www/html/logo.pngand add/var/www/html/theme.json; see Branding for the supported files and display guidance. - Validate Apache and the Shibboleth SP, make sure the cron service is running, and reload Apache. Restart
shibdonly if you changed its configuration.
For a standard systemd host, verify the required modules and configurations, start the package-installed cron service, and reload Apache:
# Debian or Ubuntu
apache2ctl -M | grep -E 'ssl|rewrite|shib|wsgi'
apache2ctl configtest
shibd -t
systemctl enable --now cron
systemctl reload apache2
# Rocky Linux or RHEL
httpd -M | grep -E 'ssl|rewrite|shib|wsgi'
httpd -t
shibd -t
systemctl enable --now crond
systemctl reload httpd
On Debian/Ubuntu, the WSGI dependency enables its module during package installation. If a host-owned module is missing, enable it with a2enmod rewrite shib ssl before repeating validation. Use the equivalent service commands on a host that does not use systemd.
Step 3: Install the Shibboleth IdP plugin
Use the signed plugin tarball in idp_plugin/ as the input to the Shibboleth plugin installer, and keep its matching .asc signature in the same directory. The installer rebuilds the IdP WAR, so stop the IdP or its container first and run the command from the IdP home directory. This example assumes /opt/shibboleth-idp.
cd /opt/shibboleth-idp
bin/plugin.sh \
-i /path/to/shibboleth-idp-plugin-passkey-<version>.tar.gz
On first installation, verify the displayed signing key before accepting the trust prompt. Use --noPrompt only when that signing key is already trusted by this IdP. Confirm the installed plugin before configuration:
bin/plugin.sh -l
After installation, update the following files on the IdP:
conf/authn/passkey.propertiesconf/authn/passkey-database.propertiesconf/authn/authn.properties- Your live MFA configuration, using
conf/authn/mfa-authn-config.xml.passkeyas the merge source
idp.authn.passkey.relyingPartyId=example.edu
idp.authn.passkey.relyingPartyOrigin=https://idp.example.edu
idp.authn.passkey.relyingPartyName=Example University IdP
idp.authn.passkey.usernameless=true
idp.authn.passkey.2fa=true
idp.authn.passkey.2fa.factorsAllowed=authn/Password
Set idp.authn.passkey.2fa=true to permit passkeys to run as a second factor after Password. This does not prevent passkeys from also being used as a primary factor; primary-factor use is controlled by the OmniPasskey default and per-device MFA rules. If a device type is marked MFA only, this setting must be true and the first factor must be listed in idp.authn.passkey.2fa.factorsAllowed, or that device type cannot authenticate.
Enable the MFA flow in conf/authn/authn.properties if your deployment will use the passkey flow through MFA:
idp.authn.flows = MFA
If passkeys are exposed through authn/MFA, replace the stock idp.authn.MFA.supportedPrincipals sample with principals that match the branches your MFA map can really satisfy. The supported principals for each flow must accurately describe the assurance it provides, and the top-level MFA list must cover the principals produced by its configured branches. Configure the SAML 2.0 and SAML 1.1 passkey classrefs consistently wherever the passkey branch is advertised or requested.
Start the IdP after configuration and confirm that it initializes without plugin, datasource, or MFA-flow errors.
Step 4: Optional inline registration
If you want users to be prompted for passkey registration immediately after a successful password-based login, enable inline registration in the IdP plugin.
idp.authn.passkey.inlineRegistration.enabled=true
idp.authn.passkey.inlineRegistration.backendUrl=https://app.example.edu/registration-server-backend/webauthn/idp/inline-registration
idp.authn.passkey.inlineRegistration.promptTargetPasskeys=1
idp.authn.passkey.inlineRegistration.promptIntervalDays=30
When this feature is enabled, allow only the IdP host or network to reach /registration-server-backend. Configure this in the <Location /registration-server-backend> block of omnipasskey.conf: replace Require all denied with a narrow Require ip or Require host rule for the IdP. The file is /etc/apache2/conf-available/omnipasskey.conf on Debian/Ubuntu, /etc/httpd/conf.d/omnipasskey.conf on Rocky/RHEL, or app/container_files/httpd/omnipasskey.conf in the container source.
Open the admin Manage Settings page at https://app.example.edu/passkey/admin/settings. In Registration Allowed Origins, include the IdP origin because inline registration runs on the IdP page, and keep the management application origin for self-service registration. Enter one origin per line and save the setting:
https://app.example.edu
https://idp.example.edu
Use your actual HTTPS origins, including any nonstandard port, and match the IdP entry to idp.authn.passkey.relyingPartyOrigin. Each hostname must equal the relying-party ID or be its subdomain. See Inline registration controls for the complete setup steps.
Step 5: Validate the deployment
https://app.example.edu/passkey/account/credentials and confirm the OmniPasskey interface loads successfully.https://app.example.edu/passkey/admin/credentials and verify access is limited to the admin roles you assigned.https://app.example.edu/passkey/helpdesk/credentials and verify access is limited to the helpdesk roles you assigned.