Protect your first app
You will run a login portal at https://localhost:8443/auth/ and protect /
with a role check. Caddy serves a short message as the example application, so
you can learn the access flow without setting up another server.
Complete Install and verify first. This example is tested with the AuthCrunch v1.5.0 distribution, caddy-security v1.5.0 and go-authcrunch v1.4.1.
This configuration includes two public demo passwords and a published signing secret.
It uses HTTPS with a local certificate and binds to 127.0.0.1. It is intended
for a disposable local directory. Read
Next steps before adapting it for a deployment.
1. Create the configuration
In your authcrunch-demo directory, create a file named Caddyfile with the
following contents. The same file is available in the
examples directory.
It adapts the distribution's end-to-end login Caddyfile with literal values, port 8443 and two local users. No environment variables or separate application files are required.
# Adapted from authcrunch/authcrunch/assets/e2e-login/Caddyfile.
# Local learning only: public demo credentials and signing key; no environment variables.
{
admin off
persist_config off
auto_https disable_redirects
skip_install_trust
on_demand_tls {
ask http://localhost:5555/
}
storage file_system ./data/caddy
security {
local identity store localdb {
realm local
path :memory:
user alice {
name Alice Example
email alice@example.test
password "LocalDemoPassword123!"
roles authp/user app/member
}
user bob {
name Bob Example
email bob@example.test
password "LocalDemoPassword123!"
roles authp/user
}
}
authentication portal myportal {
enable identity store localdb
crypto default token lifetime 300
crypto key sign-verify "LocalDemoSigningKey-ChangeBeforeDeployment-0123456789"
cookie guess domain enabled
cookie prefix AUTHTEST
trust login redirect uri domain exact localhost:8443 path prefix /
ui {
links {
"Example app" / icon "las la-external-link-alt"
}
}
}
authorization policy apppolicy {
set auth url https://localhost:8443/auth/login
crypto key verify "LocalDemoSigningKey-ChangeBeforeDeployment-0123456789"
allow roles authp/admin authp/user
set session_id cookie name AUTHTEST_SESSION_ID
set access_token cookie name AUTHTEST_ACCESS_TOKEN
}
}
}
http://localhost:5555 {
bind 127.0.0.1
@allowed_localhost query domain=localhost
respond @allowed_localhost 200
respond 400
}
https://localhost:8443 {
bind 127.0.0.1
tls internal {
on_demand
}
route /healthz {
respond "AuthCrunch demo is running." 200
}
route /auth* {
redir /auth /auth/ 308
authenticate with myportal
}
route /* {
authorize with apppolicy
respond "You reached the protected app." 200
}
}
The two users share a demo password and can both access the app:
| User | Password | Roles | Expected app access |
|---|---|---|---|
alice | LocalDemoPassword123! | authp/user, app/member | Allowed |
bob | LocalDemoPassword123! | authp/user | Allowed |
The policy accepts authp/admin or authp/user. Alice's additional app/member
role is used in the narrower-policy exercise on the next page.
The local store hashes the configured passwords when creating its user records. The clear-text examples remain in your Caddyfile, so use these credentials only for this demo.
2. Prepare local storage
mkdir -p data
The Caddyfile contains the same literal demo key in the portal's signing and policy's verification settings. Access tokens last at most 300 seconds. Use this published demo secret only for the local exercise.
Users live in memory (path :memory:) and reset when the process stops.
Caddy keeps its local CA and certificates under data/caddy, relative to the
directory from which you start the process. No user database is written there.
3. Check and run
./bin/authcrunch adapt --adapter caddyfile --config Caddyfile >/dev/null
./bin/authcrunch run --config Caddyfile
Adaptation checks whether Caddy can parse the configuration. The second command starts the server in the foreground and provisions the local user store. Leave it running while you use the browser.
Open https://localhost:8443/. Caddy asks the
loopback approval endpoint on port 5555 to approve localhost, then issues its
local certificate. The endpoint returns 400 for other or missing domains.
The browser will warn that the local CA is untrusted because skip_install_trust
prevents automatic changes to your trust store. For this local demo, use the
browser's advanced option to continue to https://localhost:8443. You should
then reach the login page. Keep localhost consistently in the address; cookies
and certificate trust do not transfer to 127.0.0.1.
4. Sign in as Alice
- Enter alice on the login page and proceed.
- Enter LocalDemoPassword123! and submit the password form.
- The trusted return destination takes you back to
/automatically. If you started directly at the portal, select Example app on its Applications page.
The app should display:
You reached the protected app.
Alice's token contains authp/user, which the policy permits. Bob has that role
too. The next page checks both logins and shows how a narrower rule denies Bob.
What you should see
These screenshots come from a Playwright run of the exact Caddyfile above with the v1.5.0 distribution on October 26, 2026. Both desktop and mobile Chromium completed the username/password flow, reached the app, and required login again after signing out. Select an image to see it at full size.

/ without a login cookie returns HTTP 302 and takes you to this form. Enter alice, then select Proceed.
Continue through the app, Applications page, and sign-out

https://localhost:8443/ with HTTP 200. This plain response is the complete demo application. Bob reaches the same response because both users have authp/user.See the incorrect-password result and the mobile layout
The browser run also checked access after a reload and in another tab, nested
app paths, a token with an altered signature, and the AUTHTEST cookie names
with Secure, HttpOnly, SameSite=Lax, and path /. Browser automation accepted
the demo's local CA warning; curl separately verified its certificate chain.
Mobile coverage is Chromium emulation. Browser logout does not establish
revocation of an independently copied token.
How the configuration fits together
The global security block defines named building blocks. The site block
connects those names to HTTP routes.
| Configuration | Purpose |
|---|---|
local identity store localdb with path :memory: | Creates the two demo users in memory |
enable identity store localdb | Gives myportal a login source |
route /auth* with authenticate with myportal | Serves the portal separately from the protected app |
set auth url https://localhost:8443/auth/login | Tells the policy where to send a browser that needs to log in |
crypto key sign-verify / crypto key verify | Connects the token issuer and verifier using the same secret |
trust login redirect uri … | Permits returning to / and its descendants on localhost:8443 |
cookie guess domain enabled / cookie prefix AUTHTEST | Infers the cookie domain and gives portal cookies the AUTHTEST prefix |
set session_id cookie name … / set access_token cookie name … | Makes the policy read the portal's AUTHTEST_SESSION_ID and AUTHTEST_ACCESS_TOKEN cookies |
tls internal { on_demand } | Issues a local certificate after the loopback endpoint approves the hostname |
allow roles authp/admin authp/user | Allows either portal role, including both demo users |
authorize with apppolicy | Checks access before the protected response |
The /healthz, /auth*, and /* route blocks keep the public health check,
portal, and protected application separate. Caddy selects the more specific
health and portal routes before the catch-all app route. Inside the app route,
authorize runs before respond; the app's response is reached only after the
policy allows the request. It covers / and all remaining application paths.
The portal's cookie prefix and the policy's cookie names must agree. The portal uses the default cookie path.
HTTPS allows the portal to use Secure cookies. The approval site at
http://localhost:5555 only approves certificate names; it does not serve login
or the app. auto_https disable_redirects avoids an extra HTTP redirect listener.
admin off disables Caddy's administration endpoint, and persist_config off
disables its saved configuration. Use
Ctrl+C to stop this foreground demo; do not use caddy reload with it.
Continue to Verify access before changing the example.




