When working with nginx to serve static content, implementing SAML 2.0 authentication presents unique challenges compared to Apache's straightforward module-based approach. Unlike Apache's mod_mellon
or mod_auth_saml
, nginx lacks native SAML support, requiring a more architectural solution.
The most effective approach combines nginx's auth_request
module with a separate authentication service. Here's the basic flow:
Client → Nginx → Auth Proxy → IdP → [Auth Success] → Content
↑
SAML Handshake
1. Nginx Configuration:
server {
listen 443 ssl;
server_name protected.domain.com;
location / {
auth_request /saml_auth;
auth_request_set $saml_user $upstream_http_x_user;
proxy_set_header X-User $saml_user;
# Your static content configuration
root /var/www/secure_content;
}
location = /saml_auth {
internal;
proxy_pass http://localhost:8000/verify;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header X-Original-URI $request_uri;
}
}
2. Authentication Service (Python Flask Example):
from flask import Flask, request
from flask_saml2.sp import IdentityProvider, ServiceProvider
app = Flask(__name__)
class ExampleServiceProvider(ServiceProvider):
def get_logout_return_url(self):
return request.url_root
def get_default_login_return_url(self):
return request.url_root
app.config['SAML2_SP'] = {
'acs_url': 'https://your.domain.com/saml/acs',
'entity_id': 'https://your.domain.com/saml/metadata.xml',
}
app.register_blueprint(ExampleServiceProvider().create_blueprint())
@app.route('/verify')
def verify_auth():
if not is_authenticated(request):
return '', 401
return '', 200
if __name__ == '__main__':
app.run(port=8000)
Option 1: Lua Scripting
For more advanced use cases, consider using nginx's lua module with libraries like lua-resty-saml
:
location /secure {
access_by_lua_block {
local saml = require "resty.saml"
local ok, err = saml.authenticate()
if not ok then
ngx.exit(ngx.HTTP_UNAUTHORIZED)
end
}
alias /path/to/static/files;
}
Option 2: OAuth2 Proxy as Middleware
While not pure SAML, many organizations use OAuth2 Proxy configured for SAML compatibility:
# In nginx.conf
location / {
auth_request /oauth2/auth;
error_page 401 = /oauth2/sign_in;
proxy_pass http://oauth2_proxy:4180;
}
- Always implement proper session timeout handling
- Configure SP-initiated logout properly
- Set appropriate cache headers for authenticated content
- Monitor both nginx and auth service metrics
Common issues and their solutions:
Issue | Solution |
---|---|
ACS URL mismatch | Verify metadata configuration |
Clock skew | Synchronize server times |
Certificate problems | Check SP and IdP cert chains |
Unlike Apache which has modules like mod_mellon
, Nginx doesn't natively support SAML authentication. The core issue revolves around:
- Nginx's lack of built-in SAML SP (Service Provider) capabilities
- The stateless nature of static content delivery
- Need for proper session management during SSO flow
The most robust approach involves using Nginx as a reverse proxy with a dedicated authentication service. Here's a typical workflow:
Client → Nginx (protected path) → Auth Service (SAML SP) → IdP → Back to Nginx with session cookie
One effective method is using OpenResty (Nginx + Lua) with a Lua SAML library:
location /protected/ {
access_by_lua_block {
local saml = require "resty.saml"
local sess = ngx.var.cookie_SESSION
if not sess then
local sp = saml.new({
entity_id = "https://your.service.provider",
idp_sso_url = "https://idp.example.com/saml2/sso",
idp_cert = [[-----BEGIN CERTIFICATE-----...-----END CERTIFICATE-----]],
assertion_consumer_service_url = "https://your.service.provider/acs"
})
ngx.redirect(sp:create_authn_request())
end
-- Validate session and proceed
}
alias /path/to/static/files;
}
For those not using OpenResty, a separate authentication service can handle SAML:
server {
listen 443 ssl;
server_name files.example.com;
location / {
proxy_pass http://auth-service:8080/validate;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header X-Original-URI $request_uri;
}
location /internal_static/ {
internal;
alias /var/www/static/;
}
}
When using an external auth service, X-Accel can be effective:
# In your auth service (Python example)
@app.route('/auth')
def auth_check():
if not validate_saml_session(request.cookies.get('session')):
return redirect_to_idp()
response = make_response()
response.headers['X-Accel-Redirect'] = '/internal_static' + request.args.get('path')
return response
Consider these mature solutions:
- SAML Service Providers:
saml2-js
,python3-saml
- Pre-built proxies:
oauth2-proxy
(with SAML plugin) - Commercial options: Pomerium, Keycloak Gatekeeper
When implementing SAML with static content:
- Cache authenticated sessions aggressively
- Use short-lived SAML assertions (5-10 minutes)
- Consider implementing a secondary lightweight auth mechanism (JWT) post-SAML auth