Authorization server example
A disposable host that operates an OAuth 2.0 authorization server and OpenID
Connect provider with plugin/authserver. A demo
relying party runs on a second origin. One TLS listener serves both hostnames:
| Origin |
Serves |
https://auth.example.test:<port> |
The issuer: the protocol endpoints at the root, auth.Handler() under /api, and the host's /login, /consent and /oauth-error pages |
https://client.example.test:<port> |
"Demo Photos", a confidential client built only on go-oidc and x/oauth2: /start begins an authorization with PKCE and a nonce, and /callback checks iss and state, redeems the code and verifies the ID token against JWKS |
The data is a temporary SQLite database, and the certificate is a throwaway
self-signed one. Both are gone when the process exits. The account
demo@example.test has the password correct horse battery staple.
Run it
go run ./examples/authserver --addr 127.0.0.1:8443
Both hostnames must resolve to the loopback address. Redirect URIs may not name
a loopback host (native clients are outside profile 1). Either add them to
/etc/hosts, or start a throwaway Chromium profile that maps them:
chromium --user-data-dir="$(mktemp -d)" --ignore-certificate-errors \
--host-resolver-rules="MAP auth.example.test 127.0.0.1, MAP client.example.test 127.0.0.1" \
https://client.example.test:8443/
Select Sign in with Example, sign in, then allow or deny on the consent
page. /start?prompt=none and /start?max_age=1 show silent authorization and
reauthentication.
What it shows
- Login page. It signs in through
POST /api/sign-in/password, then asks
POST /api/authorization/interaction what is next. Next is complete (with
the redirect), consent or login.
- Consent page. It shows only the registered client name and the
requested scopes, using
textContent so nothing is parsed as HTML. It
decides through POST /api/authorization/decide, then navigates to the
returned redirectTo. It is served with
Content-Security-Policy: frame-ancestors 'none' and X-Frame-Options: DENY.
- Error page. It receives an unknown client or an unregistered redirect
URI. Such a request is never redirected to the client.
Check it in Chromium
npm ci --ignore-scripts
make check-authserver-browser
The test (browser_test.go, tag authserver_browser) starts the host on a
random loopback port. scripts/authserver-browser.mjs then drives Chromium
through the following, and needs no database server:
- login;
- denial (
access_denied);
- consent and ID token verification;
prompt=none with a grant (a code) and without a session (login_required);
max_age forcing reauthentication with a newer auth_time;
- the error page.
This is a local demonstration. It is not a deployment recipe: the client
secret, the signing key and the deployment secret are generated in memory at
start.