Skip to content

feat: Add SAML 2.0, OpenID Connect (OIDC), and SCIM 2.0 support #339

@burning-bush-dev

Description

@burning-bush-dev

Note

This is 100% AI slop from Abe's virtual son, Moses. Feel free to add input tokens but wow did Moses go overboard.

Problem

NicTool currently supports local password auth, LDAP bind authentication, and WebAuthn/passkeys. Enterprise environments require federated identity (SAML 2.0 / OpenID Connect) for single sign-on and automated user provisioning (SCIM 2.0) for lifecycle management. Without these, NicTool cannot integrate with identity providers like Okta, Azure AD/Entra ID, Google Workspace, Keycloak, or PingIdentity without manual user management.

Research: Perl/CPAN Ecosystem (April 2026)

SAML 2.0 — Production-Ready

Library Version Status Notes
Net::SAML2 0.85 (Feb 2026) Actively maintained SP-only; 9 contributors

Capabilities: SAML 2.0 SP implementation with HTTP-Redirect, HTTP-POST, and SOAP bindings. Supports encrypted assertions, IdP metadata parsing (URL or XML), XML signature validation.

Tested IdPs: Okta, Azure AD, Google/GSuite, Keycloak, PingIdentity, ADFS, OneLogin, Shibboleth, SimpleSAMLphp, DigiD, eHerkenning, eIDAS.

Production usage: Foswiki (SamlLoginContrib), OTRS (otrs-saml2sp), Koha (library management).

OpenID Connect — Production-Ready

Library Version Status Notes
OIDC::Client 1.x (2025) Actively maintained Modern RP with discovery + JWK rotation
Crypt::JWT current Actively maintained Full JWT/JWS/JWE (RFC 7519/7515/7516)
OIDC::Lite 0.10 (2015) Legacy Functional but not recommended for new projects

OIDC::Client capabilities: Authorization code flow, all client auth methods (client_secret_basic/post/jwt, private_key_jwt), automatic JWK key rotation, token introspection, UserInfo endpoint, discovery (.well-known/openid-configuration), claim validation (iss, aud, sub, exp, etc.).

Framework plugins (all 2025+): Mojolicious::Plugin::OIDC, Catalyst::Plugin::OIDC, Dancer2::Plugin::OIDC, Plack::Auth::SSO::OIDC.

SCIM 2.0 — No CPAN Libraries Exist

No native Perl SCIM 2.0 implementation exists on CPAN (RFC 7642/7643/7644).

Reference implementations in other languages:

  • Go: elimity-com/scim (production-grade)
  • Java: SCIM-SDK (Captain-P-Goldfish)
  • Ruby: scimitar (85 stars, actively maintained)
  • Python: scim2-server (lightweight prototype)

A Perl SCIM server would need to be built from scratch using standard REST/JSON tools (Plack, JSON, Crypt::JWT for bearer token validation).

Proposed Architecture

Follow the WebAuthn integration pattern already established in the codebase — it cleanly separates browser ceremony from server verification and integrates at the Session dispatcher level.

Integration Points (existing code)

Component File Role
Auth dispatcher server/lib/NicToolServer/Session.pm::verify() Pre-session action routing
Action registry server/lib/NicToolServer.pm::api_commands() Action-to-class mapping
JSON API proxy client/htdocs/webauthn.cgi Browser-to-SOAP bridge pattern
Config storage nt_options table Feature flags and SSO settings
LDAP auth server/lib/NicToolServer/User.pm Existing external auth precedent

New Modules

Module Purpose
NicToolServer::SSO SAML assertion validation (Net::SAML2) and OIDC token verification (OIDC::Client / Crypt::JWT)
NicToolServer::SCIM SCIM 2.0 server endpoints — user/group CRUD, filtering, pagination
client/htdocs/sso.cgi SSO ceremony coordinator (initiate redirect, handle callback)

Authentication Flow

Browser                    NicTool Client           NicTool Server          IdP
  |                            |                         |                   |
  |-- Click "SSO Login" ------>|                         |                   |
  |                            |-- sso_get_login_url --->|                   |
  |                            |<-- redirect URL --------|                   |
  |<-- 302 Redirect -----------|                         |                   |
  |----------------------------------------------------->|-- AuthN -------->|
  |<-----------------------------------------------------|<-- Response -----|
  |-- POST callback ---------->|                         |                   |
  |                            |-- sso_verify ---------->|                   |
  |                            |   (validate assertion)  |                   |
  |                            |   (JIT provision user)  |                   |
  |                            |   (_create_session)     |                   |
  |                            |<-- session cookie ------|                   |
  |<-- Set-Cookie: NicTool ----|                         |                   |

Schema Changes

-- Track external identity on existing user table
ALTER TABLE nt_user ADD COLUMN auth_method
    ENUM('local','ldap','saml','oidc','scim') DEFAULT 'local';
ALTER TABLE nt_user ADD COLUMN external_id VARCHAR(255) DEFAULT NULL;
ALTER TABLE nt_user ADD COLUMN sso_provider VARCHAR(100) DEFAULT NULL;
ALTER TABLE nt_user ADD UNIQUE INDEX idx_external_id (external_id);

-- Extend session log for SSO events
ALTER TABLE nt_user_session_log MODIFY COLUMN action
    ENUM('login','logout','timeout','passkey_login','recovery_login',
         'saml_login','oidc_login') NOT NULL;

-- SSO configuration in nt_options
INSERT INTO nt_options (option_name, option_value) VALUES
    ('sso_enabled', '0'),
    ('sso_type', ''),                    -- 'saml', 'oidc', or 'both'
    ('saml_idp_metadata_url', ''),
    ('saml_sp_entity_id', ''),
    ('oidc_discovery_url', ''),
    ('oidc_client_id', ''),
    ('oidc_client_secret', ''),          -- encrypted at rest
    ('sso_auto_provision', '0'),         -- JIT user creation
    ('sso_default_group', ''),           -- group for auto-provisioned users
    ('scim_enabled', '0'),
    ('scim_bearer_token', '');           -- SCIM API auth token

Feature Requirements

Phase 1: OIDC Support

OIDC is simpler than SAML and covers most modern IdPs (Okta, Azure AD, Google, Keycloak all support it).

  • OIDC discovery endpoint integration (.well-known/openid-configuration)
  • Authorization code flow with PKCE
  • ID token validation (signature, claims: iss, aud, exp, nonce)
  • UserInfo endpoint for profile claims
  • "Sign in with SSO" button on login page
  • SSO callback handler (sso.cgi)
  • JIT user provisioning from OIDC claims (username, email, name)
  • Session creation with oidc_login audit trail
  • Admin UI for OIDC configuration (discovery URL, client ID/secret)
  • Logout: RP-initiated logout + session revocation
  • CPAN deps: OIDC::Client, Crypt::JWT

Phase 2: SAML 2.0 Support

For enterprises that require SAML (older IdPs, government, education).

  • IdP metadata import (URL or XML upload)
  • SP metadata generation endpoint
  • AuthnRequest generation (HTTP-Redirect binding)
  • Assertion Consumer Service (HTTP-POST binding)
  • XML signature and certificate validation
  • Encrypted assertion support
  • Attribute mapping (configurable: which SAML attribute maps to username/email/groups)
  • JIT user provisioning from SAML attributes
  • Session creation with saml_login audit trail
  • Single Logout (SLO) support
  • Admin UI for SAML configuration (IdP metadata, SP entity ID, certificate management)
  • CPAN deps: Net::SAML2 (v0.85+)

Phase 3: SCIM 2.0 Provisioning

For automated user lifecycle management from IdPs.

  • SCIM 2.0 server endpoints (RFC 7644):
    • GET /scim/v2/ServiceProviderConfig — capability discovery
    • GET /scim/v2/Schemas — schema discovery
    • GET /scim/v2/ResourceTypes — resource type discovery
    • POST /scim/v2/Users — create user
    • GET /scim/v2/Users/{id} — read user
    • GET /scim/v2/Users?filter=... — list/search users
    • PATCH /scim/v2/Users/{id} — partial update
    • PUT /scim/v2/Users/{id} — full replace
    • DELETE /scim/v2/Users/{id} — deactivate user
  • SCIM-to-NicTool attribute mapping (userName, name.givenName/familyName, emails, active)
  • Bearer token authentication for SCIM API
  • SCIM Group resource (map to NicTool groups)
  • Pagination (startIndex, count, totalResults)
  • Filter support (at minimum: userName eq "value", externalId eq "value")
  • Proper error responses (RFC 7644 Section 3.12)
  • Handle IdP-specific PATCH quirks (Okta, Azure AD, Google send different structures)
  • SCIM endpoint lives outside the SOAP layer (direct HTTP/JSON, like a modern REST API)
  • Admin UI for SCIM configuration (enable/disable, token management)
  • No CPAN deps — built with core Perl JSON/HTTP modules

Alternative: Reverse Proxy Approach

For deployments where code changes aren't feasible, a reverse proxy can handle SSO externally:

Proxy Protocol Notes
mod_auth_mellon SAML 2.0 Apache module, injects REMOTE_USER header
oauth2-proxy OIDC Go binary, injects X-Auth-Request-User header
Keycloak Both Full IdP + proxy, heavier deployment

This requires NicTool to trust a REMOTE_USER header for authentication — a smaller code change but with header injection risks. Could be documented as a supported deployment option alongside native integration.

Open Questions

  1. OIDC-first or SAML-first? OIDC is simpler and covers most modern IdPs. Propose OIDC first.
  2. JIT provisioning vs SCIM? JIT (create user on first SSO login) is simpler but doesn't handle deprovisioning. SCIM handles full lifecycle. Could support both.
  3. Group mapping? Should SSO claims/SCIM groups map to NicTool groups? What about permissions — auto-assign from IdP group membership or manual?
  4. Coexistence with local auth? Users should be able to have both local password and SSO, or SSO-only. The auth_method field controls this.
  5. Multi-IdP? Support one IdP at a time, or multiple simultaneously? Start with one, design for extensibility.
  6. SCIM endpoint path? Should SCIM live under the existing CGI structure or as a standalone PSGI app for cleaner REST routing?

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions