Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,17 @@ public final class io/ktor/server/auth/saml/SamlAlgorithms {
public final fun getRECOMMENDED_SIGNATURE_ALGORITHMS ()Ljava/util/Set;
}

public final class io/ktor/server/auth/saml/SamlAuthKt {
public static final fun saml (Lio/ktor/server/auth/AuthenticationConfig;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V
public static final fun saml (Lio/ktor/server/auth/AuthenticationConfig;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V
public static synthetic fun saml$default (Lio/ktor/server/auth/AuthenticationConfig;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
public static synthetic fun saml$default (Lio/ktor/server/auth/AuthenticationConfig;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
}

public final class io/ktor/server/auth/saml/SamlAuthenticationProvider : io/ktor/server/auth/AuthenticationProvider {
public fun onAuthenticate (Lio/ktor/server/auth/AuthenticationContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}

public final class io/ktor/server/auth/saml/SamlAuthnContext {
public static final field Companion Lio/ktor/server/auth/saml/SamlAuthnContext$Companion;
public static final synthetic fun box-impl (Ljava/lang/String;)Lio/ktor/server/auth/saml/SamlAuthnContext;
Expand Down Expand Up @@ -203,12 +214,41 @@ public final class io/ktor/server/auth/saml/SamlPrincipal {
public final fun hasAttribute (Ljava/lang/String;)Z
}

public final class io/ktor/server/auth/saml/SamlRedirectResult {
public fun <init> (Ljava/lang/String;Ljava/lang/String;)V
public final fun getMessageId ()Ljava/lang/String;
public final fun getRedirectUrl ()Ljava/lang/String;
}

public abstract interface class io/ktor/server/auth/saml/SamlReplayCache : java/lang/AutoCloseable {
public abstract fun isReplayed (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public abstract fun recordAssertion (Ljava/lang/String;Lkotlin/time/Instant;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public abstract fun tryRecordAssertion (Ljava/lang/String;Lkotlin/time/Instant;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}

public final class io/ktor/server/auth/saml/SamlSession {
public static final field Companion Lio/ktor/server/auth/saml/SamlSession$Companion;
public fun <init> (Ljava/lang/String;Ljava/lang/String;)V
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun getLogoutRequestId ()Ljava/lang/String;
public final fun getRequestId ()Ljava/lang/String;
}

public final synthetic class io/ktor/server/auth/saml/SamlSession$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
public static final field INSTANCE Lio/ktor/server/auth/saml/SamlSession$$serializer;
public final fun childSerializers ()[Lkotlinx/serialization/KSerializer;
public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/ktor/server/auth/saml/SamlSession;
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/ktor/server/auth/saml/SamlSession;)V
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
}

public final class io/ktor/server/auth/saml/SamlSession$Companion {
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}

public final class io/ktor/server/auth/saml/SamlSpMetadata {
public final fun getAcsUrl ()Ljava/lang/String;
public final fun getEncryptionCredential ()Lorg/opensaml/security/x509/BasicX509Credential;
Expand All @@ -234,6 +274,11 @@ public final class io/ktor/server/auth/saml/SamlSpMetadata {
public final fun technicalContact (Lkotlin/jvm/functions/Function1;)V
}

public final class io/ktor/server/auth/saml/SamlValidationException : java/lang/Exception {
public fun <init> (Ljava/lang/String;Ljava/lang/Throwable;)V
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/Throwable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
}

public final class io/ktor/server/auth/saml/SignatureAlgorithm {
public static final field Companion Lio/ktor/server/auth/saml/SignatureAlgorithm$Companion;
public fun <init> (Ljava/lang/String;Ljava/lang/String;)V
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* Copyright 2014-2026 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

package io.ktor.server.auth.saml

import io.ktor.server.auth.*

/**
* Installs SAML 2.0 authentication.
*
* SAML (Security Assertion Markup Language) 2.0 is an XML-based standard for exchanging
* authentication and authorization data between parties. This plugin implements the
* Web Browser SSO Profile for both Service Provider-initiated and Identity Provider-initiated authentication flows.
*
* ## Example Usage
*
* ```kotlin
* install(Authentication) {
* saml("saml-auth") {
* // Service Provider configuration
* sp = SamlSpMetadata {
* spEntityId = "https://myapp.example.com/saml/metadata"
* acsUrl = "https://myapp.example.com/saml/acs"
* signingCredential = SamlCrypto.loadCredential(
* keystorePath = "/path/to/keystore.jks",
* keystorePassword = "example_pass",
* keyAlias = "sp-key",
* keyPassword = "example_pass"
* )
* }
*
* // Identity Provider metadata
* idp = parseSamlIdpMetadata(idpMetadataXml)
*
* // Validation logic
* validate { credential ->
* val nameId = credential.nameId
* val email = credential.getAttribute("email")
* if (email != null) {
* SamlPrincipal(credential.assertion)
* } else {
* null
* }
* }
* }
* }
*
* routing {
* authenticate("saml-auth") {
* get("/profile") {
* val principal = call.principal<SamlPrincipal>()!!
* call.respondText("Hello ${principal.nameId}")
* }
* }
* }
* ```
*
* ## Security Features
*
* This implementation follows OWASP SAML Security Cheat Sheet recommendations:
* - **XXE Protection**: Prevents XML External Entity attacks
* - **XSW Protection**: Prevents XML Signature Wrapping
* - **Replay Protection**: Tracks processed assertion IDs to prevent replay attacks
* - **Signature Verification**: Validates XML signatures on assertions using IdP certificates
* - **Timestamp Validation**: Checks NotBefore/NotOnOrAfter with configurable clock skew
* - **Audience Restriction**: Ensures assertions are intended for this Service Provider
*
* @param name Optional name for this authentication provider
* @param configure Configuration block for SAML authentication
*
* [Report a problem](https://ktor.io/feedback/?fqname=io.ktor.server.auth.saml.saml)
*/
public fun AuthenticationConfig.saml(
name: String? = null,
configure: SamlConfig.() -> Unit
): Unit = saml(name, description = null, configure)

/**
* Installs SAML 2.0 authentication with a custom description.
*
* @param name Optional name for this authentication provider
* @param description Optional description of the provider
* @param configure Configuration block for SAML authentication
*
* @see saml
*/
public fun AuthenticationConfig.saml(
name: String? = null,
description: String? = null,
configure: SamlConfig.() -> Unit
) {
LibSaml.ensureInitialized()
val provider = SamlConfig(name, description)
.apply(configure)
.let { SamlAuthenticationProvider(it) }

register(provider)
}
Loading
Loading