• Skip to primary navigation
  • Skip to main content
  • Skip to primary sidebar
  • Skip to footer

Mister PKI

SSL Certificates * SSL Tools * Certificate Decoder

  • PKI Training
  • OpenSSL
  • Keytool
  • PKI Tools
    • Apereo CAS
    • SAML
    • OpenID Connect
  • Get expert help

Apache mod_auth_cas: Protect Web Applications with Apereo CAS and Docker

September 23, 2026 by Mister PKI Leave a Comment

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:

  1. a traditional Apache installation;
  2. a Docker architecture where Apache and mod_auth_cas run 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:

  1. CASCookiePath exists.
  2. Apache can write to it.
  3. Browser receives the module’s cookie.
  4. CASCookieSecure matches HTTPS usage.
  5. CASCookieSameSite does not block the cookie.
  6. service URLs are consistent.
  7. ticket validation succeeds.
  8. 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.

Uncategorized

Reader Interactions

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Primary Sidebar

Popular Posts

PKCS12

openssl s_client

Keytool

Keytool list

ECDSA vs RSA

OpenSSL

PKCS7

Certificate Decoder

PKI for DevOps Engineers – Free Training Series

PKI for DevOps Training Hub

Lesson 1 – How TLS and PKI Work

Lesson 2 – Understanding X.509 Certificates

Lesson 3 – Certificate Chains Explained

Lesson 4 – Debug TLS with OpenSSL

Lesson 5 – Verify Certificate Chains

Lesson 6 – Creating CSRs with OpenSSL

Lesson 7 – Working with PKCS12 Certificates

Lesson 8 – Java Keystores and keytool

Lesson 9 – Certificate Expiration Monitoring

Lesson 10 – Automating Certificate Renewal

Lesson 11 – Common TLS Errors

Lesson 12 – PKI Architecture for DevOps

Recent Posts

  • Apache mod_auth_cas: Protect Web Applications with Apereo CAS and Docker
  • Apache mod_auth_mellon: Configure SAML SSO & Docker
  • Configure OpenID Connect (OIDC) in Apereo CAS 7.3 and 8
  • Certificate Ownership Is an Operating Model Problem, Not a Tooling Problem
  • OpenSSL dgst: Create Checksums and Verify Digital Signatures

Footer

  • Twitter
  • YouTube
Mister PKI

Practical security engineering for the systems that establish trust.

Learn OpenSSL Keytool Free training
Work together PKI consulting CAS consulting Sponsored contributions
Site About Support Mister PKI Cookie policy Search
© 2026 Mister PKI Secure systems. Clear guidance.