Overview
Many web applications do not need a complete SAML or OpenID Connect implementation.
If your organization already uses Apereo CAS, Apache HTTP Server can authenticate users directly against CAS using:
mod_auth_cas
The architecture is straightforward:
Browser
|
v
Apache HTTP Server
+ mod_auth_cas
|
| CAS protocol
v
Apereo CAS
|
v
Authenticated request
|
v
Backend Application
Apache handles the CAS login redirect and service-ticket validation.
The backend application can then receive:
- the authenticated username;
- CAS attributes;
- selected trusted HTTP headers.
This is particularly useful when the application:
- does not support CAS natively;
- already runs behind Apache;
- accepts trusted authentication headers;
- is containerized;
- should not contain identity-provider-specific code.
This guide covers both:
- a traditional Apache installation;
- a Docker architecture where Apache and
mod_auth_casrun as a dedicated authentication reverse proxy.
All URLs, users, application names, directories, attributes, and domains in this article are generic.
What is mod_auth_cas?
mod_auth_cas is an Apache HTTP Server module maintained under the Apereo project.
Its purpose is to let Apache authenticate users against a server implementing the CAS protocol.
A normal authentication flow looks like:
User requests application
↓
Apache sees no CAS session
↓
Redirect to CAS login
↓
User authenticates
↓
CAS redirects back with service ticket
↓
mod_auth_cas validates ticket
↓
Apache creates local session
↓
Request reaches application
The backend does not need to validate CAS tickets itself.
The current upstream module supports Apache 2.4 or newer and CAS protocol versions 1 and 2. It can also parse the older SAML validation response format offered by CAS when configured to do so.
mod_auth_cas versus mod_auth_mellon
These modules solve similar problems but use different protocols.
mod_auth_cas
Uses:
CAS protocol
Typical flow:
Apache
↓
CAS /login
↓
service ticket
↓
CAS /serviceValidate
mod_auth_mellon
Uses:
SAML 2.0
Typical flow:
Apache SP
↓
SAML AuthnRequest
↓
Identity Provider
↓
SAML Response
If the identity provider is Apereo CAS and you only need CAS authentication, mod_auth_cas is often simpler.
If the application must participate as a standards-based SAML SP, use Mellon instead.
Real-world scenario
Assume you have an application running internally at:
http://application:8080
Users access it through:
https://portal.example.test
CAS is available at:
https://login.example.test/cas
The architecture is:
CAS protocol
+--------------------+
| |
v |
Browser ---> Apache + mod_auth_cas ---> CAS
|
| authenticated proxy request
v
Application
:8080
Only Apache should be reachable by users.
The application should remain private.
Why use Apache in front of the application?
Suppose the backend application accepts:
X-Authenticated-User: user@example.test
but does not know how to perform a CAS login.
Apache can handle that translation:
CAS authentication
↓
REMOTE_USER
↓
trusted header
↓
backend identity
This keeps CAS-specific logic outside the application.
Security warning: protect the backend
If the backend trusts:
X-Authenticated-User
then users must not be able to reach the application directly.
Otherwise, someone could send:
X-Authenticated-User: administrator
and bypass CAS entirely.
The correct architecture is:
Users
|
v
Apache + mod_auth_cas
|
private network
|
v
Application
The backend should normally have no public host port.
Step 1: Install Apache and mod_auth_cas
Debian or Ubuntu
Install:
sudo apt update
sudo apt install \
apache2 \
libapache2-mod-auth-cas \
ca-certificates \
curl
Enable the supporting Apache modules:
sudo a2enmod \
auth_cas \
proxy \
proxy_http \
headers \
ssl
If your application uses WebSockets:
sudo a2enmod proxy_wstunnel
Debian and Ubuntu package the module as:
libapache2-mod-auth-cas
Package versions may lag behind upstream releases, so check the installed package when a newer mod_auth_cas feature matters.
RHEL-style systems
The upstream project notes that mod_auth_cas is also available through Fedora and EPEL-style packaging.
Package names vary by distribution.
Confirm with:
dnf search mod_auth_cas
or:
dnf search auth-cas
Step 2: Confirm the module is loaded
Run:
apachectl -M |
grep auth_cas
Expected output resembles:
auth_cas_module (shared)
If the module was installed manually, Apache may need:
LoadModule auth_cas_module /path/to/mod_auth_cas.so
The upstream documentation explicitly requires loading the module before using:
AuthType CAS
Step 3: Create the CAS session directory
mod_auth_cas stores local session information on disk.
Create a dedicated directory:
sudo mkdir -p \
/var/cache/apache2/mod_auth_cas
Make it writable only by the Apache service account:
sudo chown \
www-data:www-data \
/var/cache/apache2/mod_auth_cas
sudo chmod 700 \
/var/cache/apache2/mod_auth_cas
Then configure:
CASCookiePath /var/cache/apache2/mod_auth_cas/
This directory is security-sensitive.
The upstream project warns that anyone who can write there can potentially falsify local CAS authentication session information.
Do not use a world-writable directory.
Step 4: Configure the CAS login URL
Set:
CASLoginURL \
https://login.example.test/cas/login
When an unauthenticated user requests a protected resource, Apache redirects the browser there.
mod_auth_cas automatically adds the CAS:
service=
parameter.
The request conceptually becomes:
https://login.example.test/cas/login
?service=https://portal.example.test/
Step 5: Configure ticket validation
Use:
CASValidateURL \
https://login.example.test/cas/serviceValidate
The upstream module requires the validation URL to use:
https://
because ticket validation over an unencrypted connection would expose the application to a man-in-the-middle attack.
The normal CAS 2 flow uses:
/serviceValidate
rather than:
/validate
because CAS 2 responses can include attributes in addition to the username.
Step 6: Configure the CAS protocol version
The upstream default is:
CASVersion 2
You can make it explicit:
CASVersion 2
CAS 2 is normally preferable because it supports richer validation responses.
A minimal global configuration now looks like:
CASVersion 2
CASCookiePath \
/var/cache/apache2/mod_auth_cas/
CASLoginURL \
https://login.example.test/cas/login
CASValidateURL \
https://login.example.test/cas/serviceValidate
Step 7: Trust the CAS server certificate
mod_auth_cas must validate the HTTPS certificate used by:
CASLoginURL
CASValidateURL
The upstream directive is:
CASCertificatePath
For a normal publicly trusted certificate, the system CA directory may already work:
CASCertificatePath /etc/ssl/certs/
For a private CA, use a CA certificate file:
CASCertificatePath \
/etc/apache2/cas/cas-ca.pem
or a hashed CA directory.
Do not disable certificate validation just because CAS uses an internal PKI.
Install the correct trust anchor instead.
For hashed directories, see:
openssl rehash
in the Mister PKI OpenSSL series.
Step 8: Protect a URL
The simplest protected location is:
<Location />
AuthType CAS
Require valid-user
</Location>
Now all requests require CAS authentication.
The authentication flow becomes:
GET /
↓
AuthType CAS
↓
No local CAS session
↓
redirect to CAS
After ticket validation, Apache sets:
REMOTE_USER
to the username returned by CAS.
Step 9: Protect only part of an application
You do not have to protect the entire site.
For example:
<Location /secure>
AuthType CAS
Require valid-user
</Location>
The application may expose:
/public
anonymously and:
/secure
through CAS.
This is useful for applications containing both public and authenticated content.
Step 10: Understand the local CAS session
CAS itself issues a one-time:
service ticket
during login.
After successful validation, mod_auth_cas creates its own local Apache session.
Conceptually:
CAS ticket
↓
validate once
↓
mod_auth_cas session
↓
local session cookie
This avoids contacting CAS for every HTTP request.
The local session information is stored under:
CASCookiePath
Step 11: Configure session lifetime
The module supports:
CASTimeout
which controls the hard lifetime of a local mod_auth_cas session.
For example:
CASTimeout 28800
represents:
8 hours
After that, the module obtains a new CAS service ticket.
You can also control idle timeout behavior with:
CASIdleTimeout
if appropriate for your deployment.
The correct values depend on:
- CAS session policy;
- application sensitivity;
- user experience;
- organizational security requirements.
Step 12: Configure secure cookies
Current mod_auth_cas supports cookie flags including:
CASCookieSecure
CASCookieHttpOnly
CASCookieSameSite
A reasonable HTTPS configuration is:
CASCookieSecure On
CASCookieHttpOnly On
CASCookieSameSite Lax
Use:
None
or:
Strict
only after testing the application and CAS redirect flow.
The module’s default HttpOnly setting is enabled, while CASCookieSecure can automatically follow the connection type.
Explicit configuration can make the intended security policy clearer.
Step 13: Forward the authenticated username
Apache always makes the CAS username available as:
REMOTE_USER
The module can also place that user into an HTTP request header using:
CASAuthNHeader
For example:
CASAuthNHeader X-Authenticated-User
Now the backend can receive:
X-Authenticated-User: user123
This is useful for applications that already support trusted reverse-proxy authentication.
Step 14: Scrub client-supplied authentication headers
This is critical.
If you configure:
CASAuthNHeader X-Authenticated-User
also enable:
CASScrubRequestHeaders On
The upstream documentation states that this removes incoming request headers with special meaning, including the authentication header and CAS attribute headers.
That prevents a browser from sending:
X-Authenticated-User: administrator
and having the application trust it.
The trust model should be:
Client header
↓
removed
CAS identity
↓
mod_auth_cas
↓
trusted header
A strong baseline is:
CASAuthNHeader X-Authenticated-User
CASScrubRequestHeaders On
Step 15: Forward CAS attributes
CAS 2 validation responses can contain attributes.
For example:
mail
displayName
groups
When:
CASAuthNHeader
is enabled, mod_auth_cas can expose those attributes as HTTP headers.
The prefix is controlled by:
CASAttributePrefix
For Apache 2.4, use a valid HTTP-header-style prefix containing letters and hyphens.
For example:
CASAttributePrefix CAS-
A CAS attribute:
mail
can then appear as:
CAS-mail: user@example.test
The upstream documentation specifically warns against underscores in the prefix with Apache 2.4 because headers containing invalid characters may be silently dropped.
Step 16: Handle multi-valued CAS attributes
Suppose CAS returns:
groups:
app-users
app-editors
app-admins
mod_auth_cas combines multiple values using:
CASAttributeDelimiter
The default is:
,
So the header may look like:
CAS-groups: app-users,app-editors,app-admins
You can choose another delimiter:
CASAttributeDelimiter ";"
producing:
CAS-groups: app-users;app-editors;app-admins
Choose a delimiter that does not naturally occur inside the attribute values.
For raw LDAP DNs such as:
CN=app-users,OU=Groups,DC=example,DC=test
a comma is a poor delimiter.
A better upstream design is to have CAS release simplified application-specific group names.
Step 17: Authorize based on a CAS attribute
mod_auth_cas can perform Apache authorization based on returned attributes.
For example:
<Location /admin>
AuthType CAS
Require cas-attribute role:administrator
</Location>
You can also use regular-expression matching:
Require cas-attribute groups~^app-admin
The upstream documentation notes that attribute names and literal values are case-sensitive.
For complicated authorization logic, it may still be cleaner to:
authenticate in Apache
authorize in the application
rather than duplicating business logic in Apache configuration.
Step 18: Configure forced reauthentication
Some application paths may require fresh credentials.
Use:
CASRenew
For example:
CASRenew /sensitive/
Accessing that relative path forces CAS credential renewal.
This can be useful for:
- administrative actions;
- sensitive account changes;
- privileged operations.
The path must be relative according to the module documentation.
Step 19: Understand CAS Gateway mode
The module also supports:
CASGateway
for paths where anonymous access is allowed but CAS authentication should be detected when available.
Conceptually:
Already logged into CAS?
|
+--- yes ---> identify user
|
+--- no ----> continue anonymously
This can be useful for sites that personalize content for authenticated users without requiring login.
Do not use Gateway mode when the application genuinely requires authentication.
Step 20: Configure Apache as a reverse proxy
Now proxy authenticated requests to the application:
ProxyPreserveHost On
ProxyPass \
/ \
http://application:8080/
ProxyPassReverse \
/ \
http://application:8080/
The backend receives only requests that have already passed through Apache authentication.
Step 21: Complete Apache configuration
A practical virtual host may look like:
<VirtualHost *:443>
ServerName portal.example.test
SSLEngine On
SSLCertificateFile \
/etc/apache2/tls/server.crt
SSLCertificateKeyFile \
/etc/apache2/tls/server.key
SSLProtocol -all +TLSv1.2 +TLSv1.3
CASVersion 2
CASCookiePath \
/var/cache/apache2/mod_auth_cas/
CASLoginURL \
https://login.example.test/cas/login
CASValidateURL \
https://login.example.test/cas/serviceValidate
CASCertificatePath \
/etc/ssl/certs/
CASTimeout 28800
CASCookieSecure On
CASCookieHttpOnly On
CASCookieSameSite Lax
CASAuthNHeader \
X-Authenticated-User
CASAttributePrefix \
CAS-
CASAttributeDelimiter ";"
CASScrubRequestHeaders On
<Location />
AuthType CAS
Require valid-user
</Location>
ProxyPreserveHost On
ProxyPass \
/ \
http://application:8080/ \
timeout=3600
ProxyPassReverse \
/ \
http://application:8080/
ErrorLog ${APACHE_LOG_DIR}/portal-error.log
CustomLog ${APACHE_LOG_DIR}/portal-access.log combined
</VirtualHost>
This provides:
HTTPS
CAS authentication
local CAS session
header scrubbing
username forwarding
attribute forwarding
reverse proxy
Step 22: Register the application in CAS
CAS will normally require the application URL to exist in its service registry.
A generic JSON registration might resemble:
{
"@class": "org.apereo.cas.services.CasRegisteredService",
"serviceId": "^https://portal\\.example\\.test/.*",
"name": "Example Apache CAS Application",
"id": 1001,
"evaluationOrder": 1001
}
The exact registered-service structure depends on:
- CAS version;
- service registry type;
- attribute-release requirements.
Keep the service pattern as narrow as practical.
Avoid:
"serviceId": ".*"
for a production service.
Step 23: Release attributes from CAS
If the application needs attributes such as:
mail
displayName
groups
CAS must actually release them during service validation.
The flow is:
Identity source
↓
CAS principal attributes
↓
service attribute-release policy
↓
CAS serviceValidate response
↓
mod_auth_cas
↓
HTTP headers
If Apache does not see a particular attribute, verify CAS releases it before changing Apache.
Step 24: CAS 2 versus SAML validation mode
mod_auth_cas also supports:
CASValidateSAML On
with a validation endpoint resembling:
/cas/samlValidate
This uses the SAML response format supported by older CAS protocol integrations.
It is important not to confuse this with:
SAML 2.0 Service Provider support
mod_auth_cas is not a replacement for mod_auth_mellon.
If you need a true SAML 2.0 SP:
use mod_auth_mellon
For normal Apereo CAS integration:
CASVersion 2
/serviceValidate
is usually the simpler choice.
Step 25: CASRootProxiedAs for reverse-proxy environments
If another proxy or load balancer sits in front of Apache, Apache may see an internal URL that differs from what users see.
For example:
User sees:
https://portal.example.test/
Apache sees:
http://apache:8080/
That can cause CAS to generate or validate the wrong:
service=
URL.
mod_auth_cas provides:
CASRootProxiedAs
to override automatic service URL generation.
For example:
CASRootProxiedAs \
https://portal.example.test
This tells mod_auth_cas to construct externally visible service URLs using the public origin.
This is especially important behind:
- load balancers;
- ingress controllers;
- TLS-offloading proxies.
Step 26: Docker architecture
Now move Apache and mod_auth_cas into a container.
The architecture becomes:
Browser
|
| HTTPS
v
Apache + mod_auth_cas
Docker container
|
| private Docker network
v
Application container
CAS remains external:
Apache container
|
| HTTPS service validation
v
CAS server
Only the Apache proxy is exposed to users.
Step 27: Docker directory structure
Create:
cas-proxy/
├── Dockerfile
├── apache.conf
├── entrypoint.sh
└── tls/
├── server.crt
└── server.key
If CAS uses a private CA, also include:
cas-proxy/
└── trust/
└── cas-ca.pem
Do not commit:
server.key
to Git.
Step 28: Dockerfile using the distro package
A Debian-based example:
FROM debian:13-slim
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
apache2 \
libapache2-mod-auth-cas \
ca-certificates \
curl \
&& a2enmod \
auth_cas \
ssl \
proxy \
proxy_http \
proxy_wstunnel \
headers \
&& a2dissite 000-default \
&& mkdir -p /var/cache/apache2/mod_auth_cas \
&& chown www-data:www-data \
/var/cache/apache2/mod_auth_cas \
&& chmod 700 \
/var/cache/apache2/mod_auth_cas \
&& rm -rf /var/lib/apt/lists/*
COPY apache.conf \
/etc/apache2/sites-available/application.conf
COPY entrypoint.sh \
/usr/local/bin/entrypoint.sh
RUN a2ensite application \
&& chmod +x \
/usr/local/bin/entrypoint.sh
EXPOSE 8443
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
This is the simplest Docker approach.
The exact package version comes from the selected Debian release, so confirm it with:
dpkg -s libapache2-mod-auth-cas |
grep Version
if upstream 1.3 behavior is required.
Step 29: Docker Apache configuration
Use:
Listen 8443
<VirtualHost *:8443>
ServerName portal.example.test
SSLEngine On
SSLCertificateFile \
/etc/apache2/tls/server.crt
SSLCertificateKeyFile \
/etc/apache2/tls/server.key
SSLProtocol -all +TLSv1.2 +TLSv1.3
CASVersion 2
CASCookiePath \
/var/cache/apache2/mod_auth_cas/
CASLoginURL \
https://login.example.test/cas/login
CASValidateURL \
https://login.example.test/cas/serviceValidate
CASCertificatePath \
/etc/ssl/certs/
CASTimeout 28800
CASCookieSecure On
CASCookieHttpOnly On
CASCookieSameSite Lax
CASAuthNHeader \
X-Authenticated-User
CASAttributePrefix \
CAS-
CASAttributeDelimiter ";"
CASScrubRequestHeaders On
<Location />
AuthType CAS
Require valid-user
</Location>
ProxyPreserveHost On
ProxyPass \
/ \
http://application:8080/ \
nocanon \
timeout=3600
ProxyPassReverse \
/ \
http://application:8080/
ErrorLog /dev/stderr
CustomLog /dev/stdout combined
</VirtualHost>
Docker DNS resolves:
application
to the backend container.
Step 30: Docker entrypoint
Create:
#!/usr/bin/env bash
set -euo pipefail
COOKIE_DIR="/var/cache/apache2/mod_auth_cas"
mkdir -p "$COOKIE_DIR"
chown www-data:www-data "$COOKIE_DIR"
chmod 700 "$COOKIE_DIR"
required_files=(
/etc/apache2/tls/server.crt
/etc/apache2/tls/server.key
)
for file in "${required_files[@]}"; do
if [[ ! -s "$file" ]]; then
echo "ERROR: Missing required file: $file" >&2
exit 1
fi
done
chmod 600 /etc/apache2/tls/server.key
apache2ctl configtest
exec apache2ctl -D FOREGROUND
The important part for mod_auth_cas is:
CASCookiePath
must exist and be writable when Apache starts.
Step 31: Docker Compose example
Use:
services:
application:
image: example/application:latest
restart: unless-stopped
networks:
- app_network
# No public port.
# Requests must pass through Apache/CAS.
cas-proxy:
build:
context: ./cas-proxy
restart: unless-stopped
depends_on:
- application
ports:
- "443:8443"
volumes:
- ./cas-proxy/tls:/etc/apache2/tls:ro
networks:
- app_network
networks:
app_network:
The critical security feature is:
application:
no ports
cas-proxy:
443 exposed
Users cannot connect directly to the trusted-header backend.
Step 32: Persisting the mod_auth_cas session directory
By default:
/var/cache/apache2/mod_auth_cas
lives inside the container.
If the container is recreated, local CAS sessions disappear.
That is not usually catastrophic.
Users with an active CAS SSO session are redirected to CAS and typically returned without entering credentials again.
If preserving local sessions matters, add a Docker volume:
volumes:
cas_sessions:
Then:
services:
cas-proxy:
volumes:
- cas_sessions:/var/cache/apache2/mod_auth_cas
- ./cas-proxy/tls:/etc/apache2/tls:ro
Treat that volume as authentication data.
Do not expose it to other containers unnecessarily.
Step 33: Multiple Apache containers
mod_auth_cas stores its local sessions in files.
That matters if you run:
cas-proxy-1
cas-proxy-2
cas-proxy-3
behind a load balancer.
A browser might authenticate through node 1 and then land on node 2, where the local session file does not exist.
Possible designs include:
sticky sessions
or a carefully managed shared session filesystem.
Do not assume mod_auth_cas provides a distributed session store.
For many smaller deployments, one highly available reverse-proxy layer or load-balancer affinity is simpler.
Step 34: Docker with CAS behind a private CA
If the CAS server uses an internal CA, mount the CA certificate:
volumes:
- ./cas-proxy/trust/cas-ca.pem:/usr/local/share/ca-certificates/cas-ca.crt:ro
Then update trust during the image build or entrypoint.
A cleaner Dockerfile pattern is:
COPY trust/cas-ca.crt \
/usr/local/share/ca-certificates/cas-ca.crt
RUN update-ca-certificates
Then:
CASCertificatePath /etc/ssl/certs/
can use the normal system trust store.
This is usually cleaner than pointing CASCertificatePath at a one-off file.
Step 35: Do not use curl -k-style trust bypasses
The CAS validation channel is authentication infrastructure.
Do not solve:
certificate verify failed
by bypassing TLS verification.
Instead:
install correct CA
verify hostname
verify chain
use correct CAS URL
The upstream module explicitly requires ticket validation over SSL because unprotected validation would expose the service to man-in-the-middle attacks.
Step 36: Health checking the Docker proxy
A basic health check can confirm Apache is responding:
healthcheck:
test:
[
"CMD-SHELL",
"curl -fsSk -o /dev/null https://localhost:8443/"
]
interval: 30s
timeout: 10s
retries: 5
start_period: 20s
However, a protected root URL may return a CAS redirect.
That can still indicate Apache is functioning.
For example:
curl -skI \
https://localhost:8443/
might show:
HTTP/1.1 302 Found
Location: https://login.example.test/cas/login?service=...
That is a useful proxy-level health signal.
It does not prove CAS ticket validation is working end-to-end.
Step 37: Test the initial CAS redirect
Run:
curl -kI \
https://portal.example.test/
Expected:
HTTP/1.1 302 Found
with a location containing:
https://login.example.test/cas/login
and:
service=https...
Inspect the service URL carefully.
It must match the application URL registered in CAS.
Step 38: Troubleshoot incorrect service URLs
One of the most common CAS integration problems is:
CAS sees:
https://portal.example.test/
mod_auth_cas sends:
http://apache:8443/
CAS may reject the ticket because the service used during validation does not match the service associated with the ticket.
If Apache is behind another proxy, use:
CASRootProxiedAs \
https://portal.example.test
and ensure:
ServerName portal.example.test
also reflects the public site.
Step 39: Troubleshoot redirect loops
Symptoms:
Application
↓
CAS
↓
Application
↓
CAS
↓
...
Check:
- CASCookiePath exists.
- Apache can write to it.
- Browser receives the module’s cookie.
CASCookieSecurematches HTTPS usage.CASCookieSameSitedoes not block the cookie.- service URLs are consistent.
- ticket validation succeeds.
- CAS server certificate is trusted.
Enable debugging temporarily:
CASDebug On
LogLevel debug
Restart Apache and inspect the error log.
Do not leave excessive authentication debugging enabled indefinitely.
Step 40: Troubleshoot CAS certificate validation
An error involving:
certificate verify failed
or:
issuer certificate is invalid
usually means the container or host cannot validate the HTTPS certificate used by CAS.
Inspect the CAS endpoint:
openssl s_client \
-connect login.example.test:443 \
-servername login.example.test \
-showcerts
Then verify the chain:
openssl verify \
-CAfile trusted-ca.pem \
cas-server.crt
Do not point CASCertificatePath at an unrelated server certificate when the module actually needs the appropriate trust anchor.
Step 41: Troubleshoot 403 after login
If CAS authentication succeeds but Apache returns:
403 Forbidden
check the authorization rules.
For example:
Require cas-attribute groups:app-users
will fail if:
groups
was not returned or the value differs.
Temporarily simplify:
Require valid-user
If that works, authentication is fine and the problem is authorization or attribute release.
Step 42: Troubleshoot missing CAS attributes
If:
X-Authenticated-User
works but:
CAS-mail
does not exist, check:
1. Does CAS resolve mail?
2. Does the CAS service release mail?
3. Does serviceValidate contain mail?
4. Is CASAuthNHeader configured?
5. Is CASAttributePrefix valid?
6. Is CASScrubRequestHeaders removing only inbound copies?
Remember that CASAttributePrefix is only used when:
CASAuthNHeader
is enabled.
Step 43: Inspect CAS serviceValidate manually
For troubleshooting, you can inspect the CAS validation response using a real short-lived ticket, though tickets are normally consumed only once.
A CAS 2 response may resemble:
<cas:serviceResponse>
<cas:authenticationSuccess>
<cas:user>user123</cas:user>
<cas:attributes>
<cas:mail>user@example.test</cas:mail>
<cas:groups>app-users</cas:groups>
</cas:attributes>
</cas:authenticationSuccess>
</cas:serviceResponse>
If the attribute is absent there, the problem is upstream of Apache.
Step 44: Understand CASAuthNHeader versus REMOTE_USER
Even without:
CASAuthNHeader
Apache sets:
REMOTE_USER
after successful CAS authentication.
Applications integrated directly with Apache may already understand:
REMOTE_USER
If so, you may not need a custom authentication header at all.
Use:
CASAuthNHeader
only when the backend architecture requires an HTTP header.
Reducing custom headers reduces the authentication attack surface.
Step 45: Single Sign-Out limitation
The current mod_auth_cas project lists CAS Single Sign-Out as experimental/problematic and warns that it may interfere with POST request data.
The upstream README also lists single sign-out as a known limitation.
Do not design a critical session-security workflow assuming:
CAS logout
↓
instant reliable destruction
of every mod_auth_cas session
without testing the exact version you deploy.
If strong centralized logout semantics are mandatory, evaluate that requirement explicitly before choosing mod_auth_cas.
Step 46: Proxy authentication limitation
The upstream project currently lists:
CAS Proxy Validation
as not implemented.
That means mod_auth_cas is best suited to:
browser
↓
Apache application
authentication.
It is not the right tool when the application must obtain CAS proxy tickets to act on behalf of the authenticated user against another CAS-protected backend service.
For that use case, use a CAS client library that implements proxy authentication.
Step 47: mod_auth_cas and CAS 3.x servers
The server may be a modern CAS 7.x or 8.x deployment while the client module uses:
CASVersion 2
That is normal.
CASVersion refers to the protocol version used between:
mod_auth_cas
and:
the CAS validation endpoint
not the release number of the CAS server.
So:
CAS server 7.x
+
CASVersion 2
is perfectly sensible.
Likewise:
CAS server 8.x
+
CASVersion 2
can work as long as the CAS protocol endpoints remain enabled and the application is registered.
Step 48: Apache 2.4 header naming
Avoid:
CASAttributePrefix CAS_
on Apache 2.4.
The upstream project warns that header names containing invalid characters such as underscores may be silently dropped.
Prefer:
CASAttributePrefix CAS-
Then:
mail
becomes:
CAS-mail
rather than:
CAS_mail
This is especially important when migrating an older Apache 2.2 configuration.
Step 49: Complete Docker example
Dockerfile
FROM debian:13-slim
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
apache2 \
libapache2-mod-auth-cas \
ca-certificates \
curl \
&& a2enmod \
auth_cas \
ssl \
proxy \
proxy_http \
proxy_wstunnel \
headers \
&& a2dissite 000-default \
&& mkdir -p \
/var/cache/apache2/mod_auth_cas \
&& chown www-data:www-data \
/var/cache/apache2/mod_auth_cas \
&& chmod 700 \
/var/cache/apache2/mod_auth_cas \
&& rm -rf /var/lib/apt/lists/*
COPY apache.conf \
/etc/apache2/sites-available/application.conf
COPY entrypoint.sh \
/usr/local/bin/entrypoint.sh
RUN a2ensite application \
&& chmod +x \
/usr/local/bin/entrypoint.sh
EXPOSE 8443
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
entrypoint.sh
#!/usr/bin/env bash
set -euo pipefail
COOKIE_DIR="/var/cache/apache2/mod_auth_cas"
mkdir -p "$COOKIE_DIR"
chown www-data:www-data "$COOKIE_DIR"
chmod 700 "$COOKIE_DIR"
apache2ctl configtest
exec apache2ctl -D FOREGROUND
apache.conf
Listen 8443
<VirtualHost *:8443>
ServerName portal.example.test
SSLEngine On
SSLCertificateFile \
/etc/apache2/tls/server.crt
SSLCertificateKeyFile \
/etc/apache2/tls/server.key
SSLProtocol -all +TLSv1.2 +TLSv1.3
CASVersion 2
CASCookiePath \
/var/cache/apache2/mod_auth_cas/
CASLoginURL \
https://login.example.test/cas/login
CASValidateURL \
https://login.example.test/cas/serviceValidate
CASCertificatePath \
/etc/ssl/certs/
CASTimeout 28800
CASCookieSecure On
CASCookieHttpOnly On
CASCookieSameSite Lax
CASAuthNHeader \
X-Authenticated-User
CASAttributePrefix \
CAS-
CASAttributeDelimiter ";"
CASScrubRequestHeaders On
<Location />
AuthType CAS
Require valid-user
</Location>
ProxyPreserveHost On
ProxyPass \
/ \
http://application:8080/ \
nocanon \
timeout=3600
ProxyPassReverse \
/ \
http://application:8080/
ErrorLog /dev/stderr
CustomLog /dev/stdout combined
</VirtualHost>
compose.yml
services:
application:
image: example/application:latest
restart: unless-stopped
networks:
- application_network
cas-proxy:
build:
context: ./cas-proxy
restart: unless-stopped
depends_on:
- application
ports:
- "443:8443"
volumes:
- ./cas-proxy/tls:/etc/apache2/tls:ro
networks:
- application_network
volumes:
cas_sessions:
networks:
application_network:
If persistent module sessions are desired:
volumes:
- ./cas-proxy/tls:/etc/apache2/tls:ro
- cas_sessions:/var/cache/apache2/mod_auth_cas
Docker security checklist
Before deploying:
[ ] Backend has no public port
[ ] Apache is the only application entry point
[ ] CAS validation uses HTTPS
[ ] CAS certificate chain is trusted
[ ] CASCookiePath is writable only by Apache
[ ] CASScrubRequestHeaders is enabled
[ ] CASAuthNHeader uses an application-specific name
[ ] CASAttributePrefix uses hyphens, not underscores
[ ] CAS attributes are minimized
[ ] Multi-valued attribute delimiter is safe
[ ] Public service URL is registered in CAS
[ ] CASRootProxiedAs is configured when needed
[ ] Session behavior after container restart is understood
[ ] Multi-replica session behavior is understood
[ ] Single Sign-Out limitations are understood
Frequently asked questions
What is mod_auth_cas?
mod_auth_cas is an Apache authentication module that allows Apache HTTP Server to authenticate users against an Apereo CAS server.
Does mod_auth_cas work with Apache 2.4?
Yes.
Current upstream version 1.3 requires Apache 2.4 or newer.
Which CAS protocol should I use?
For most current installations:
CASVersion 2
with:
/cas/serviceValidate
is appropriate.
Can mod_auth_cas work with CAS 7 or CAS 8?
Yes.
The module protocol version and CAS server software version are different concepts.
A modern CAS server can continue serving CAS 2 protocol endpoints.
What does CASCookiePath do?
It stores local mod_auth_cas session information.
For example:
CASCookiePath \
/var/cache/apache2/mod_auth_cas/
The directory should be writable only by Apache.
How do I pass the authenticated user to an application?
Use:
CASAuthNHeader \
X-Authenticated-User
The backend receives the authenticated CAS username in that header.
Should I enable CASScrubRequestHeaders?
Yes when using trusted CAS headers.
It removes incoming client-supplied copies of authentication and attribute headers before mod_auth_cas creates trusted values.
How do I pass CAS attributes as headers?
Enable:
CASAuthNHeader \
X-Authenticated-User
then choose:
CASAttributePrefix CAS-
CAS attributes can then be presented as headers such as:
CAS-mail
CAS-groups
How are multi-valued attributes handled?
Use:
CASAttributeDelimiter ";"
to control how multiple values are joined.
Can mod_auth_cas authorize based on attributes?
Yes.
For example:
Require cas-attribute role:administrator
or regex matching with:
Require cas-attribute groups~^app-admin
Can mod_auth_cas run in Docker?
Yes.
A useful pattern is:
Apache/mod_auth_cas container
↓
private Docker network
↓
application container
Only the Apache container should normally expose a public port.
Do mod_auth_cas sessions survive container recreation?
Only if the session directory is persisted.
Otherwise, users receive a new local session after the container restarts.
An existing CAS SSO session often makes that reauthentication transparent.
Can I run multiple mod_auth_cas replicas?
Yes, but local session files are node-specific unless you deliberately provide shared storage.
Load-balancer affinity may therefore be useful.
Does mod_auth_cas support CAS Single Sign-Out?
The current upstream project lists Single Sign-Out as experimental/problematic and warns that POST data may be affected.
Test carefully before depending on it.
Does mod_auth_cas support CAS proxy tickets?
The current upstream README lists proxy validation as unimplemented.
If your application needs proxy-ticket behavior, use a CAS client library designed for that feature.
Is mod_auth_cas a SAML 2.0 module?
No.
It can parse the SAML validation response format offered by CAS, but it is not a general SAML 2.0 Service Provider.
Use:
mod_auth_mellon
for a true Apache SAML 2.0 SP.
Related Mister PKI guides
- Configure a SAML Service Provider with Apache mod_auth_mellon.
- Configure Apereo CAS OpenID Connect for applications with native OIDC support.
- Learn about Apereo CAS Single Logout.
- Troubleshoot TLS trust with OpenSSL s_client.
- Build hashed trust directories using OpenSSL rehash.
- Verify CAS certificates with OpenSSL verify.
- Browse the complete OpenSSL Commands Guide.
External references
- Apereo mod_auth_cas project
- mod_auth_cas README and configuration reference
- Apereo CAS Protocol Specification
- Apache mod_proxy documentation
- Apache mod_headers documentation
- Docker Compose documentation
Conclusion
mod_auth_cas provides a clean way to place Apereo CAS authentication in front of a web application without adding CAS client code to the application itself.
The architecture is:
Browser
↓
Apache
↓
mod_auth_cas
↔
Apereo CAS
↓
authenticated request
↓
application
The most important Apache directives are:
CASCookiePath \
/var/cache/apache2/mod_auth_cas/
CASLoginURL \
https://login.example.test/cas/login
CASValidateURL \
https://login.example.test/cas/serviceValidate
CASVersion 2
and:
<Location />
AuthType CAS
Require valid-user
</Location>
When a backend application needs a trusted identity header:
CASAuthNHeader \
X-Authenticated-User
CASScrubRequestHeaders On
should be treated as a pair.
The Docker model is equally straightforward:
Users
↓
Apache + mod_auth_cas container
↓
private Docker network
↓
application container
The backend should not expose a public port when it trusts identity information from Apache.
Finally, understand the scope of the module.
mod_auth_cas is excellent for normal browser-to-application CAS authentication, but current upstream documentation still calls out limitations around CAS proxy validation and Single Sign-Out.
For applications with native OIDC support, direct CAS OIDC integration may be simpler. For SAML 2.0 applications, Apache mod_auth_mellon is the more appropriate protocol bridge.
For applications that need straightforward CAS SSO in front of Apache, however, mod_auth_cas remains a compact and practical solution.
Leave a Reply