Skip to content
Open
Show file tree
Hide file tree
Changes from 5 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
1 change: 1 addition & 0 deletions apisix/cli/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ local _M = {
"chaitin-waf",
"multi-auth",
"openid-connect",
"saml-auth",
"cas-auth",
"authz-casbin",
"authz-casdoor",
Expand Down
136 changes: 136 additions & 0 deletions apisix/plugins/saml-auth.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
--
-- Licensed to the Apache Software Foundation (ASF) under one or more
-- contributor license agreements. See the NOTICE file distributed with
-- this work for additional information regarding copyright ownership.
-- The ASF licenses this file to You under the Apache License, Version 2.0
-- (the "License"); you may not use this file except in compliance with
-- the License. You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
local require = require
local pcall = pcall
local core = require("apisix.core")
local constants = require("apisix.constants")

local is_resty_saml_init = false
local resty_saml

local lrucache = core.lrucache.new({
ttl = 300, count = 512
})

local schema = {
type = "object",
properties = {
sp_issuer = { type = "string" },
idp_uri = { type = "string" },
idp_cert = { type = "string" },
login_callback_uri = { type = "string" },
Comment thread
AlinsRan marked this conversation as resolved.
logout_uri = { type = "string" },
logout_callback_uri = { type = "string" },
logout_redirect_uri = { type = "string" },
sp_cert = { type = "string" },
sp_private_key = { type = "string" },
auth_protocol_binding_method = {
type = "string",
default = "HTTP-Redirect",
enum = {"HTTP-Redirect", "HTTP-POST",},
description = "Binding method for authentication protocol, setting to HTTP-POST " ..
"will set cookie samesite to None and cookie secure to true"
},
secret = {
type = "string",
description = "Secret used for key derivation.",
minLength = 8,
maxLength = 32,
},
secret_fallbacks = {
type = "array",
items = {
type = "string",
minLength = 8,
maxLength = 32,
},
description = "List of secrets for alternative secrets used when doing key rotation"
}
},
encrypt_fields = {"sp_private_key", "secret", "secret_fallbacks"},
required = {
"sp_issuer",
"idp_uri",
"idp_cert",
"login_callback_uri",
"logout_uri",
"logout_callback_uri",
"logout_redirect_uri",
"sp_cert",
"sp_private_key",
}
}

local plugin_name = "saml-auth"

local _M = {
version = 0.1,
priority = 2598,
name = plugin_name,
schema = schema,
}


local function load_resty_saml()
if resty_saml then
return resty_saml
end

local ok, saml = pcall(require, "resty.saml")
if not ok then
return nil, saml
end

resty_saml = saml
return resty_saml
end

function _M.check_schema(conf, schema_type)
Comment thread
AlinsRan marked this conversation as resolved.
Outdated
return core.schema.check(schema, conf)
end

function _M.rewrite(conf, ctx)
local saml_lib, err = load_resty_saml()
if not saml_lib then
core.log.error("failed to load lua-resty-saml: ", err)
return 503, {message = "lua-resty-saml is required for saml-auth"}
end

if not is_resty_saml_init then
local err = saml_lib.init({
debug = true,
data_dir = constants.apisix_lua_home .. "/deps/share/lua/5.1/resty/saml"
Comment thread
AlinsRan marked this conversation as resolved.
Outdated
})
if err then
core.log.error("saml init: ", err)
return 503, {message = "saml init failed"}
end
is_resty_saml_init = true
end

local saml = core.lrucache.plugin_ctx(lrucache, ctx, nil, saml_lib.new, conf)
if not saml then
core.log.error("saml new failed")
return 500, {message = "create saml object failed"}
end

local data = saml:authenticate()
Comment thread
AlinsRan marked this conversation as resolved.
Outdated

ctx.external_user = data
end

return _M
1 change: 1 addition & 0 deletions conf/config.yaml.example
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,7 @@ plugins: # plugin list (sorted by priority)
- chaitin-waf # priority: 2700
- multi-auth # priority: 2600
- openid-connect # priority: 2599
- saml-auth # priority: 2598
- cas-auth # priority: 2597
- authz-casbin # priority: 2560
- authz-casdoor # priority: 2559
Expand Down
3 changes: 2 additions & 1 deletion docs/en/latest/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,8 @@
"plugins/ldap-auth",
"plugins/opa",
"plugins/forward-auth",
"plugins/multi-auth"
"plugins/multi-auth",
"plugins/saml-auth"
]
},
{
Expand Down
153 changes: 153 additions & 0 deletions docs/en/latest/plugins/saml-auth.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
---
title: saml-auth
keywords:
- Apache APISIX
- API Gateway
- SAML
- SAML 2.0
- SSO
- Single Sign-On
description: The saml-auth Plugin enables SAML 2.0 authentication for API routes, integrating with external Identity Providers (IdP) such as Keycloak, Okta, and Azure Active Directory.
---

<!--
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
-->

<head>
<link rel="canonical" href="https://docs.api7.ai/hub/saml-auth" />
</head>

## Description

The `saml-auth` Plugin enables [SAML 2.0](https://docs.oasis-open.org/security/saml/Post2.0/sstc-saml-tech-overview-2.0.html) (Security Assertion Markup Language) authentication for API routes. It acts as a SAML Service Provider (SP) and integrates with external Identity Providers (IdP) such as Keycloak, Okta, and Azure Active Directory to authenticate users before allowing access to upstream resources.

When a request arrives at a protected route, the Plugin checks for a valid SAML session. If no session exists, it redirects the user to the IdP for authentication. After the user authenticates at the IdP, the IdP posts a signed SAML assertion back to the SP's Assertion Consumer Service (ACS) URL. The Plugin validates the assertion and establishes a session for the user.

The Plugin supports:

- **HTTP-Redirect binding** (default) — SAML messages are transmitted as URL query parameters.
- **HTTP-POST binding** — SAML messages are transmitted as HTML form values.
- **Single Logout (SLO)** — logout requests can be initiated by the SP or the IdP.
- **Session key rotation** via `secret_fallbacks`.

Authenticated user data is stored in `ctx.external_user` and can be used by downstream authorization plugins such as `acl`.

## Attributes

| Name | Type | Required | Encrypted | Default | Valid Values | Description |
|------|------|----------|-----------|---------|--------------|-------------|
| sp_issuer | string | True | | | | Service Provider (SP) entity ID/issuer URI. Must match the SP entity ID registered with the IdP. |
Comment thread
AlinsRan marked this conversation as resolved.
| idp_uri | string | True | | | | Identity Provider SSO endpoint URL. This is the URL to which SAML authentication requests are sent. |
| idp_cert | string | True | | | | IdP's X.509 certificate in PEM format, used to verify signatures on SAML assertions. |
| login_callback_uri | string | True | | | | SP's Assertion Consumer Service (ACS) URL. The IdP posts SAML responses to this URL after authentication. Must be registered with the IdP. |
| logout_uri | string | True | | | | SP's Single Logout (SLO) endpoint. Requests to this URI initiate the logout flow. |
| logout_callback_uri | string | True | | | | SP's SLO callback URL. The IdP sends logout responses to this URL. Must be registered with the IdP. |
| logout_redirect_uri | string | True | | | | URL to redirect users to after a successful logout. |
| sp_cert | string | True | | | | SP's X.509 certificate in PEM format. Used by the IdP to verify requests signed by the SP. |
| sp_private_key | string | True | Yes | | | SP's private key in PEM format, used to sign SAML requests. This field is encrypted at rest. |
| auth_protocol_binding_method | string | False | | `HTTP-Redirect` | `HTTP-Redirect`, `HTTP-POST` | SAML binding method for the authentication request. When set to `HTTP-POST`, the session cookie `SameSite` attribute is set to `None` and `Secure` is set to `true`. |
| secret | string | False | Yes | | 8–32 characters | Secret used for session key derivation. This field is encrypted at rest. |
| secret_fallbacks | array[string] | False | Yes | | Each item: 8–32 characters | List of previous secrets used during key rotation. Allows sessions encrypted with old secrets to remain valid. This field is encrypted at rest. |

## Prerequisites

Install `lua-resty-saml` on every APISIX node before enabling this Plugin:

```shell
luarocks install lua-resty-saml 0.2.5
```

`lua-resty-saml` builds native xmlsec bindings, so the build environment must provide the OpenSSL, libxml2, and libxslt development files required by LuaRocks.

Before configuring the `saml-auth` Plugin, you need to register APISIX as a Service Provider with your Identity Provider. The exact steps depend on your IdP; the following example uses [Keycloak](https://www.keycloak.org/).

### Set Up Keycloak

1. Log in to the Keycloak Admin Console.
2. Create or select a realm (for example, `myrealm`).
3. Navigate to **Clients** and click **Create client**.
4. Set **Client type** to `SAML`.
5. Set **Client ID** to match the `sp_issuer` value you will use in the Plugin configuration (for example, `https://sp.example.com`).
6. Under **Client** > **Settings**:
- Set **Root URL** to `https://sp.example.com`.
- Set **Valid redirect URIs** to include the `login_callback_uri` (for example, `https://sp.example.com/login/callback`).
- Set **Master SAML Processing URL** to `https://sp.example.com/login/callback`.
7. Under **Client** > **Keys**, upload the SP certificate (`sp_cert`) and enable **Sign assertions**.
8. Export the IdP metadata to obtain the `idp_uri` (SSO URL) and `idp_cert` (signing certificate).
9. Create users in Keycloak that will be allowed to authenticate.

## Enable the Plugin

The following example creates a route protected by the `saml-auth` Plugin using a Keycloak IdP:

:::note

Replace the placeholder certificate and key values with your actual SP certificate, SP private key, and IdP certificate.

:::

```shell
curl "http://127.0.0.1:9180/apisix/admin/routes/1" \
-H "X-API-KEY: $ADMIN_API_KEY" \
-X PUT \
-d '{
"uri": "/*",
"plugins": {
"saml-auth": {
"sp_issuer": "https://sp.example.com",
"idp_uri": "https://keycloak.example.com/realms/myrealm/protocol/saml",
"idp_cert": "-----BEGIN CERTIFICATE-----\nMIIC...\n-----END CERTIFICATE-----",
"login_callback_uri": "https://sp.example.com/login/callback",
"logout_uri": "https://sp.example.com/logout",
"logout_callback_uri": "https://sp.example.com/logout/callback",
"logout_redirect_uri": "https://sp.example.com/logout/done",
"sp_cert": "-----BEGIN CERTIFICATE-----\nMIIC...\n-----END CERTIFICATE-----",
"sp_private_key": "-----BEGIN RSA PRIVATE KEY-----\nMIIE...\n-----END RSA PRIVATE KEY-----",
"auth_protocol_binding_method": "HTTP-Redirect",
"secret": "my-session-secret"
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
}
}'
```

## Disable the Plugin

To disable the `saml-auth` Plugin, remove it from the route configuration:

```shell
curl "http://127.0.0.1:9180/apisix/admin/routes/1" \
-H "X-API-KEY: $ADMIN_API_KEY" \
-X PUT \
-d '{
"uri": "/*",
"plugins": {},
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
}
}'
```
3 changes: 2 additions & 1 deletion docs/zh/latest/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,8 @@
"plugins/ldap-auth",
"plugins/opa",
"plugins/forward-auth",
"plugins/multi-auth"
"plugins/multi-auth",
"plugins/saml-auth"
]
},
{
Expand Down
Loading
Loading