Expand description
Authentication for axum servers and Yew frontends: password, Cloudflare Access and Google sign-in, sessions and user management.
The design, including the full API each layer grows into, is in
docs/design.md.
§Features
Each layer sits behind a cargo feature, so an app takes only what it uses.
Server-only and browser-only dependencies never share a feature, so a wasm
frontend can take yew023 without pulling in server code. Only std is on
by default. On docs.rs, items that need a feature are marked with it.
The crate is no_std. With default-features = false and neither std
nor alloc, it has only the plain types (UserId, Role, Access,
Method, Conflict, AuthConfig, MIN_PASSWORD_LEN, is_same_origin_path); alloc adds the
rest of types. The server layers and yew023 imply std.
A dependency that is not yet 1.0 and appears in the API is named after its
version: its feature (and module, if it has one) is axum08 for axum
0.8, sqll015 for sqll 0.15, musli02 for musli 0.2, musliweb05 for
musli-web 0.5, phrasebookcatalog01 for phrasebook-catalog 0.1 and
yew023 for yew 0.23. A later version is added beside it, so a watchword
upgrade never forces an app onto a new axum, sqll, musli, musli-web,
phrasebook-catalog or yew,
and an app moves when it chooses.
| Feature | Module | What it provides |
|---|---|---|
| (none) | types | UserId, Role, Access, Method, Conflict, AuthConfig, MIN_PASSWORD_LEN and is_same_origin_path, from the watchword-core crate |
alloc | types | the wire types (UserInfo, LoginRequest, CreatedUser, …), AuthError and its Invalid reasons, validate_password, normalize_email |
std (default) | alloc, and the standard library for the dependencies that have it | |
musli02 | musli 0.2 Encode/Decode on the types | |
musliweb05 | musliweb05 | for musli-web 0.5: the account, user-management and Cloudflare Access settings endpoints and the AuthBroadcast of live auth changes, as API types an app carries on its own musli-web WebSocket, with their ids pinned in 32000–32255; with axum08, Connection, which answers those endpoints on one socket and turns AuthEvents into the updates it may see; with yew023, AuthContext::set_socket, through which the components follow those broadcasts (implies alloc and musli02) |
server | core, store | bcrypt password hashing with dummy_hash timing, session ids and link tokens, HMAC-signed session cookies; the Auth service (sign-in, registration links, user management, sign-in throttle, AuthEvents and actor-attributed AuditEvents, bootstrap admin) and Auth::guard for WebSocket sessions; the Store trait and MemoryStore (implies std) |
cloudflare | cloudflare | Cloudflare Access JWT verification with a pluggable KeyFetcher; Auth::cloudflare and Auth::set_cloudflare, which keep the settings in the store; with axum08, admin routes for them (implies server) |
google | google | Google OAuth: GoogleConfig::new for AuthBuilder::google; with axum08, routes() serves its start, registration, account-switch and callback routes (implies server) |
axum08 | axum08 | for axum 0.8: the SignedIn, Admin, MaybeSignedIn and LiveSession extractors and routes() for the /api/auth JSON routes, plus the Cloudflare and Google routes when those features are on (implies server) |
sqll015 | sqll 0.15 BindValue, Bind and FromColumn on UserId (an integer with the same bits), Role, Access and Method (their names), from the watchword-core crate, for an app’s own Store over SQLite such as examples/sqll-store; no storage of its own | |
test-util | store_suite!, google::fake | for an app’s tests: Auth::test_user and Auth::test_session, an active user and a session without signing in; store_suite!, watchword’s store conformance suite, to run over the app’s own Store; with google, AuthBuilder::google_endpoints, and with google and axum08 also FakeGoogle, a local fake Google to sign in through offline (implies server) |
phrasebookcatalog01 | phrasebookcatalog01 | L10N, watchword’s messages (the text of the yew023 components) as a phrasebook-catalog 0.1 catalog, which phrasebook 0.2 takes, for a native tool such as an app’s localization checker that needs them without building yew, from the watchword-core crate (needs neither std nor alloc) |
yew023 | yew023 | for yew 0.23: AuthProvider, use_auth, SignIn, Register, AccountPanel, UserMenu, AdminUsers, AdminCloudflare, CloudflareSettings, the http client and L10N, the components’ messages as a phrasebook catalog, from the watchword-yew023 crate (implies std and phrasebookcatalog01) |
The most-used names that do not depend on a versioned feature are re-exported at the crate root.
§Using it
Add it from crates.io with the features each crate needs. An app whose
frontend talks to its server over a musli-web 0.5 WebSocket, as the
example app does, takes musliweb05 on both sides: the
server answers the components’ requests and sends them live updates on
that socket, and the frontend hands the socket to the components. An app
that uses only HTTP leaves it out of both.
# The server crate.
[dependencies]
watchword = { version = "0.3", features = ["axum08", "cloudflare", "google", "musliweb05"] }
[dev-dependencies]
watchword = { version = "0.3", features = ["test-util"] }# The frontend crate, built for wasm32-unknown-unknown.
[dependencies]
watchword = { version = "0.3", features = ["yew023", "musliweb05"] }Take only the sign-in methods the app offers: cloudflare and google
each add one, and password sign-in is always there. test-util, under
dev-dependencies, gives the app’s tests users and sessions without going
through sign-in, and the store conformance suite.
§Storage
watchword keeps users, sessions, registration links and settings through
the Store trait, which each app implements over its own database, with
its own tables and migrations: the shape of user data differs between
apps, and only the app knows how to fill its own columns. watchword ships
no tables or migrations, only MemoryStore, which keeps everything in
memory, for tests and trying things out.
Start from examples/sqll-store, a complete Store over SQLite with
sqll 0.15 (taking the sqll015 feature), and change its tables to the
app’s. The Store docs say what each method must do atomically, and the
store conformance suite, store_suite! (feature test-util), checks
the result from the app’s tests:
cargo test --example sqll-store --features test-util,sqll015The examples here refer to the crate as watchword. An app may give the
dependency a shorter key, such as auth = { package = "watchword", ... },
and then writes auth:: where these docs write watchword::.
Then build an Auth over a store and serve its routes, as the axum08
module shows, and wrap the frontend in AuthProvider, as the yew023
module shows.
§Example app
examples/app is an axum server and a Yew frontend wired up with all three
sign-in methods, MemoryStore and a musli-web WebSocket held open for the
session, which carries the components’ account and admin requests and live
updates beside the app’s own API (musliweb05): a reference for apps taking
the library, and a way to try the components by hand. It is its own cargo
workspace, so it adds nothing to the library’s builds or lockfile.
Build the frontend with trunk, then run the server, which serves it:
cd examples/app/frontend && trunk build
cd examples/app && cargo run -p watchword-example-server -- --bind 127.0.0.1:8080The server prints a registration link for the bootstrap admin (admin by
default, --bootstrap-admin NAME to change it); open it to choose a password
and sign in. Everything is in memory, so a restart starts over. Google sign-in
is on when GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET are set, with the
OAuth client’s redirect URL at PUBLIC_URL/api/auth/google/redirect
(--public-url, by default http:// plus the bind address). Cloudflare Access
is configured on the app’s Settings page, which watchword serves, or from
CLOUDFLARE_TEAM_DOMAIN and CLOUDFLARE_AUDIENCE until saved there; CLOUDFLARE_TRUST_EMAIL_HEADER=1 accepts Access’s email
header alone, for local testing only. --help lists the options.
trunk builds with the wasm-bindgen CLI matching examples/app/Cargo.lock,
downloading it unless it is installed or already in trunk’s cache.
Re-exports§
pub use self::core::Actor;serverpub use self::core::AuditEvent;serverpub use self::core::Auth;serverpub use self::core::AuthBuilder;serverpub use self::core::AuthEvent;serverpub use self::core::Bootstrap;serverpub use self::core::BootstrapAdmin;serverpub use self::core::CookieConfig;serverpub use self::core::PasswordError;serverpub use self::core::SESSION_LIFETIME;serverpub use self::core::Secure;serverpub use self::core::SessionGuard;serverpub use self::core::TOKEN_LIFETIME;serverpub use self::core::dummy_hash;serverpub use self::core::generate_session_key;serverpub use self::core::hash_password;serverpub use self::core::is_expired;serverpub use self::core::load_or_create_key;serverpub use self::core::new_session_id;serverpub use self::core::new_token;serverpub use self::core::session_expiry;serverpub use self::core::token_expiry;serverpub use self::core::verify_password;serverpub use self::store::MemoryStore;serverpub use self::store::Store;serverpub use self::google::GoogleConfig;googlepub use self::google::GoogleConfigError;google
Modules§
- axum08
axum08 - axum 0.8 integration (the
axum08feature): extractors and the/api/authJSON routes. A WebSocket watches its session withAuth::guard, which needs no axum. - cloudflare
cloudflare - Identifying users behind Cloudflare Access.
- core
server - The server core: password hashing, session ids and link tokens, signed
session cookies, and the
Authservice that enforces the sign-in and user-management rules over aStore. - google
google - Signing in with Google OAuth (the
googlefeature). - musliweb05
musliweb05 - watchword’s account and user-management operations, and its live updates, as musli-web 0.5 API types, so an app can carry them on the musli-web WebSocket it already has.
- phrasebookcatalog01
phrasebookcatalog01 - watchword’s messages as a phrasebook-catalog 0.1 catalog,
L10N. - store
server - Storage of users, sessions, link tokens and settings: the
Storetrait, the records it stores, andMemoryStore. - types
- yew023
yew023
Macros§
- store_
suite test-util - Defines the store conformance suite over a
Store: one#[test]function per test ofAuththat depends on what a store keeps, each over a fresh store made by the expression given. The expression is evaluated inside the test’s async context, so it may.await, and the store must beClone, its clones sharing their records.
Structs§
- Auth
Config - What the sign-in page offers, from
GET /api/auth/config. - Cloudflare
Config alloc - The Cloudflare Access settings an app stores and edits.
- Created
User alloc - The answer to
POST /api/auth/users: the new user and their registration link, whose token is shown only this once. - Impersonator
alloc - The admin behind an impersonated session; see
UserInfo::impersonated_by. - Link
alloc - A registration or reset link’s token, shown once when issued.
- Login
Request alloc - The body of
POST /api/auth/login. - NewUser
alloc - A user to create. At least one of
loginandemailis required. - Password
Change alloc - The body of
POST /api/auth/account/password. - Register
Info alloc - What a registration or reset link is for, from
GET /api/auth/register/{token}. - Register
Request alloc - The body of
POST /api/auth/register/{token}. - User
Change alloc - A change to a user.
Noneleaves a field as it is;Some(None)clears an optional one. - User
Entry alloc - A user in the admin list.
- UserId
- A user’s id. Apps convert to and from their own
u64ids withnewandgetorFrom, and to and from signed integers withTryFrom, which fails on a value out of the other’s range. With thesqll015feature it binds and reads as a sqll column directly. - User
Info alloc - What the frontend and the admin pages see of a user.
Enums§
- Access
- A user’s account state.
- Auth
Error alloc - The error of an
/api/authroute, sent as the JSON body of its 4xx response, such as{"error":"conflict","detail":"email"}or{"error":"invalid","detail":{"reason":"password_too_short","min":8}}. - Conflict
- Which unique field a change conflicts on.
- Invalid
alloc - Why a request is invalid, the detail of
AuthError::Invalid. - Method
- The credential a user has stored. A user has exactly one, and can switch between them. Cloudflare Access is not a stored credential.
- Role
- A user’s role.
Constants§
- MIN_
PASSWORD_ LEN - The shortest password
validate_passwordaccepts, in bytes.
Functions§
- is_
same_ origin_ path - Whether
path, a?next=value once decoded, is a path on this origin, and so safe to send a browser to after signing in. - normalize_
email alloc - Trims and lowercases an email so lookups and comparisons are case-insensitive.
- validate_
password alloc - Returns why the password is not acceptable, if it is not.