Skip to content

General: Add SAML2 redirect URI support for external client authentication#12534

Draft
Predixx wants to merge 11 commits intodevelopfrom
feature/general/saml2-sso-redirect-uri
Draft

General: Add SAML2 redirect URI support for external client authentication#12534
Predixx wants to merge 11 commits intodevelopfrom
feature/general/saml2-sso-redirect-uri

Conversation

@Predixx
Copy link
Copy Markdown
Contributor

@Predixx Predixx commented Apr 14, 2026

Summary

Adds support for external clients (VS Code extension, iOS/Android apps) to receive a JWT after SAML2 SSO login via an optional redirect_uri parameter. After successful SAML2 authentication, Artemis redirects to the external client's custom URI scheme (e.g., vscode://) with the JWT as a query parameter. Without the parameter, the existing web SAML2 flow is completely preserved.

Also adds spring-boot-starter-security-saml2 dependency, which is required since Spring Boot 4 moved the SAML2 auto-configuration (Saml2RelyingPartyAutoConfiguration) to a separate module. Without this starter, the RelyingPartyRegistrationRepository bean is not created and the SAML2 profile fails to start.

Checklist

General

Server

  • Important: I implemented the changes with a very good performance and prevented too many (unnecessary) and too complex database calls.
  • I strictly followed the principle of data economy for all database calls.
  • I strictly followed the server coding and design guidelines and the REST API guidelines.
  • I added multiple integration tests (Spring) related to the features (with a high test coverage).
  • I added pre-authorization annotations according to the guidelines and checked the course groups for all new REST Calls (security).
  • I documented the Java code using JavaDoc style.

Motivation and Context

Institutions using SAML2 SSO (e.g., Shibboleth, Keycloak) currently have no way for external clients to authenticate. Users on SAML2-only instances must use the "forgot password" workaround to set a password for API access. This change enables a standard OAuth2-like redirect flow for native clients using custom URI schemes.

Related issues: #10967, #10968

Description

Flow:

  1. External client opens browser: {artemisUrl}/saml2/authenticate/{registrationId}?redirect_uri=vscode://artemis/callback
  2. Artemis validates the redirect_uri, stores it in Hazelcast with a UUID nonce, sets nonce as SAML2 RelayState
  3. Normal SAML2 IdP authentication flow
  4. On success, custom AuthenticationSuccessHandler looks up the redirect_uri from Hazelcast, mints a JWT, redirects to vscode://artemis/callback?jwt=<token>
  5. Without redirect_uri, the existing web flow is completely unchanged

Security:

  • Redirect URIs are validated against a configurable scheme allowlist (saml2.allowed-redirect-schemes)
  • http/https schemes are always blocked (prevents open redirects)
  • Nonces are stored in Hazelcast (cluster-safe), consumed atomically on first use (replay protection), TTL 5 min
  • The redirect_uri is never placed in RelayState (only an opaque nonce)

New components:

  • SAML2ExternalClientAuthenticationSuccessHandler -- extends SimpleUrlAuthenticationSuccessHandler
  • HazelcastSaml2RedirectUriRepository -- distributed nonce store
  • SAML2RedirectUriValidator -- URI validation logic

Spring Boot 4 fix:

  • Added spring-boot-starter-security-saml2 dependency (replaces direct spring-security-saml2-service-provider dependency). Spring Boot 4 moved the SAML2 auto-configuration to a separate module; without the starter, the RelyingPartyRegistrationRepository bean is not created and the saml2 profile fails to start.

Configuration (disabled by default):

saml2:
  allowed-redirect-schemes: []  # e.g., ['vscode', 'artemis-ios']
  external-token-remember-me: false

Deployers who want external client authentication must explicitly configure which URI schemes they trust. This is an opt-in feature following the same pattern as OAuth2 redirect URI allowlists.

Steps for Testing

Prerequisites:

  • Docker (for Keycloak)
  • OpenSSL (for generating signing credentials)

Test 1: Integration Tests (automated)

./gradlew test --tests "de.tum.cit.aet.artemis.core.authentication.PasskeySaml2IntegrationTest" -x webapp
./gradlew test --tests "de.tum.cit.aet.artemis.core.authentication.SAML2ExternalClientRedirectIntegrationTest" -x webapp

Test 2: Manual SAML2 + Redirect URI (with Keycloak)

  1. Start Keycloak:

    docker compose -f docker/saml-test.yml --env-file .env up -d

    Verify the admin console is reachable at http://localhost:9080 (admin/admin).

  2. Generate SAML2 signing credentials:

    openssl req -x509 -newkey rsa:2048 -keyout saml-key.pem -out saml-cert.crt -days 365 -nodes -subj "/CN=artemis"
    openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in saml-key.pem -out saml-key-pkcs8.pem
  3. Add the following to your application-local.yml:

    spring:
        security:
            saml2:
                relyingparty:
                    registration:
                        keycloak:
                            entity-id: artemis
                            signing:
                                credentials:
                                    - private-key-location: file:<ABSOLUTE_PATH_TO>/saml-key-pkcs8.pem
                                      certificate-location: file:<ABSOLUTE_PATH_TO>/saml-cert.crt
                            assertingparty:
                                metadata-uri: http://localhost:9080/realms/artemis/protocol/saml/descriptor
    
    saml2:
        username-pattern: 'saml2-{first_name}_{last_name}'
        first-name-pattern: '{first_name}'
        last-name-pattern: '{last_name}'
        email-pattern: '{email}'
        registration-number-pattern: '{uid}'
        allowed-redirect-schemes:
            - vscode
            - artemis-ios
        identity-providers:
            - metadata: http://localhost:9080/realms/artemis/protocol/saml/descriptor
              registration-id: keycloak
              entity-id: artemis
    
    info.saml2:
        buttonLabel: 'SAML2 Login'
        passwordLoginDisabled: false
        enablePassword: true

    Replace <ABSOLUTE_PATH_TO> with the actual absolute paths to the generated files.

  4. Start Artemis with the saml2 profile. Make sure saml2 comes before local in the profile list so application-local.yml overrides application-saml2.yml:

    SPRING_PROFILES_ACTIVE=artemis,scheduling,core,dev,saml2,local,localci,localvc,iris
    

    Note: Do not include the buildagent profile together with saml2, as buildagent excludes the JPA auto-configuration.

  5. Test redirect flow: Open browser at http://localhost:8080/saml2/authenticate/keycloak?redirect_uri=vscode://artemis/callback, log in as saml2user1 / password. Verify the browser redirects to vscode://artemis/callback?jwt=<valid-jwt>.

  6. Test normal web flow: Open http://localhost:8080/saml2/authenticate/keycloak (without redirect_uri), log in. Verify you are redirected to / (the Artemis dashboard), not to an external URI.

  7. Test invalid schemes: Open http://localhost:8080/saml2/authenticate/keycloak?redirect_uri=https://evil.com/callback. Verify the https scheme is rejected (no RelayState in the SAML request, user lands on / after login).

  8. Test password login: Verify normal password login (artemis_admin / artemis_admin) still works.

Testserver States

N/A (infrastructure/config change, no testserver deployment needed)

Review Progress

Code Review

  • Code Review 1
  • Code Review 2

Manual Tests

  • Test 1
  • Test 2

Test Coverage

Server

Class/File Line Coverage Lines
SAML2Configuration.java 67.44% 90
SAML2Properties.java 62.32% 159
HazelcastSaml2RedirectUriRepository.java 100.00% 50
SAML2ExternalClientAuthenticationSuccessHandler.java 91.67% 74
SAML2RedirectUriValidator.java 100.00% 47

Last updated: 2026-04-16 11:10:16 UTC

@github-project-automation github-project-automation bot moved this to Work In Progress in Artemis Development Apr 14, 2026
@github-actions github-actions bot added tests server Pull requests that update Java code. (Added Automatically!) config-change Pull requests that change the config in a way that they require a deployment via Ansible. core Pull requests that affect the corresponding module labels Apr 14, 2026
@github-actions
Copy link
Copy Markdown

@Predixx Test coverage has been automatically updated in the PR description.

@github-actions
Copy link
Copy Markdown

@Predixx Test coverage has been automatically updated in the PR description.

@Predixx Predixx changed the title General: Add SAML2 redirect URI support for external client authentication General: Add SAML2 redirect URI support for external client authentication Apr 14, 2026
@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 14, 2026

End-to-End Test Results

Phase Status Details
All Tests ❌ Failed
TestsPassed ☑️Skipped ⚠️Failed ❌️Time ⏱
All E2E Tests Report (PR)253 ran250 passed2 skipped1 failed35m 50s

Test Strategy: Running all tests (configuration or infrastructure changes detected)

Overall: ❌ E2E tests failed

🔗 Workflow Run · 📊 Test Report

@github-actions
Copy link
Copy Markdown

@Predixx Test coverage has been automatically updated in the PR description.

@github-actions github-actions bot removed the docker label Apr 16, 2026
@github-actions
Copy link
Copy Markdown

@Predixx Test coverage has been automatically updated in the PR description.

…uration

Spring Boot 4 moved Saml2RelyingPartyAutoConfiguration to a separate
module (spring-boot-security-saml2). Add the starter dependency so
the RelyingPartyRegistrationRepository bean is auto-configured from
spring.security.saml2.relyingparty.registration properties.
@Predixx Predixx force-pushed the feature/general/saml2-sso-redirect-uri branch from 4e9fc46 to 0db273b Compare April 16, 2026 10:58
@github-actions
Copy link
Copy Markdown

@Predixx Test coverage has been automatically updated in the PR description.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

config-change Pull requests that change the config in a way that they require a deployment via Ansible. core Pull requests that affect the corresponding module server Pull requests that update Java code. (Added Automatically!) tests

Projects

Status: Work In Progress

Development

Successfully merging this pull request may close these issues.

1 participant