CLI installation and backend integration
Documentation index · Shared sign-in · Publishing
The public awwwth npm package adds an authentication gateway to an existing HTTP backend. The gateway runs on Node.js 22+ alongside your application, so the application itself can use any backend language or framework. It does not rewrite framework routes or install framework dependencies.
Requirements
- Node.js 22+ and npm on the application host.
- A running Awwwth identity server with the shared-sign-in changes deployed.
- A registered application with a client ID, client secret, and exact callback URL.
- An HTTP backend reachable privately from the gateway.
- HTTPS for production public origins; loopback HTTP is allowed for development.
The CLI cannot register trusted applications anonymously. Use the Awwwth account interface to register a company application as its owner, or a platform application as an administrator. For API-based registration, see the API reference.
Install in an existing project
For local development, choose these addresses:
| Component | Address |
|---|---|
| Identity server | http://localhost:4100 |
| Public application gateway | http://localhost:3000 |
| Private application backend | http://127.0.0.1:3001 |
| Registered callback | http://localhost:3000/auth/awwwth/callback |
Register that callback in Awwwth, save the returned client secret, and run this inside your application directory:
npx awwwth \
--issuer http://localhost:4100 \
--app-origin http://localhost:3000 \
--upstream http://127.0.0.1:3001 \
--client-id app_REPLACE_WITH_YOUR_ID
Enter the client secret at the hidden prompt. With no arguments, npx awwwth prompts for the issuer and credentials and uses the application/backend defaults above.
Start your existing backend at its private address using your framework's own command. In another terminal, start the generated gateway:
node --env-file=.env.awwwth .awwwth/server.mjs
If your project has a package.json, the installer also adds:
npm run awwwth
Open the gateway URL, http://localhost:3000, in your browser. It starts sign-in automatically when no valid app session exists. Complete account verification if required.
The gateway owns the app's entry points for account access. Link sign-in,
sign-up, and password-help buttons to /auth/awwwth/login,
/auth/awwwth/signup, and /auth/awwwth/forgot. Common GET routes
/login, /sign-in, /signup, /sign-up, /register, /forgot, and
/forgot-password also open the hosted Awwwth screens. For example,
/auth/awwwth/signup?returnTo=/checkout returns to checkout after the new
account is verified and authorized. Only local returnTo paths are accepted.
The hosted flow also handles MFA and passkeys. Your application should stop
submitting credentials to its old login and registration APIs and use the
verified sub header for its own permissions and account records.
Files and changes
| File | Behavior |
|---|---|
.awwwth/server.mjs | Adds the standalone HTTP gateway |
.awwwth/config.mjs | Adds environment validation |
.awwwth/oidc.mjs | Adds ID-token verification and cookie encryption |
.awwwth/widget.mjs, .awwwth/widget.css | Adds the reusable modal and styles |
.env.awwwth | Stores local credentials and a generated session encryption key; file mode 0600 |
.env.awwwth.example | Adds a configuration template with secret values omitted |
AWWWTH.md | Adds application-specific setup and usage instructions |
.gitignore | Appends a rule excluding .env.awwwth |
package.json | Adds an awwwth script if a manifest already exists |
Existing application code and existing scripts are preserved. A conflicting generated file, existing .awwwth directory, or existing awwwth script stops installation before changes are made. Non-Node projects do not receive a new package.json; use the standalone Node command.
The generated runtime is copied into your project. Publishing a newer npm version does not automatically update existing copies. Review upgrades deliberately and preserve the app's session secret unless you intend to invalidate its cookies.
Use the same modal in any frontend
Add this to a shared page layout, then use the buttons wherever account access belongs in the interface:
<script src="/auth/awwwth/widget.js" defer></script>
<button type="button" data-awwwth="login">Sign in</button>
<button type="button" data-awwwth="signup">Create account</button>
<button type="button" data-awwwth="forgot">Forgot password?</button>
The widget adapts to small screens, light/dark color schemes, keyboard input,
and reduced-motion settings. Override --awwwth-accent, --awwwth-surface,
--awwwth-text, --awwwth-muted, or --awwwth-border on the page to match
an app's design. A button can set data-awwwth-return-to="/checkout" for a
local post-login destination. The actual account form opens in a secure popup
on the Awwwth origin; the overlay shows progress and errors. Awwwth cannot be
embedded in an iframe because its account cookies, passkeys, and frame policy
require its own origin.
For framework code, call window.Awwwth.open({ screen: 'login', reload: false })
from a user click, await the returned user, and update the app's auth state.
window.Awwwth.session() reads the current user, and
window.Awwwth.logout() revokes this app's session. The widget dispatches
awwwth:authenticated when sign-in succeeds. The default behavior reloads the
page so ordinary server-rendered apps pick up the new session automatically.
CLI options
| Option | Purpose |
|---|---|
--issuer URL | Canonical Awwwth browser origin |
--api-origin URL | Separate OAuth API origin, if the issuer advertises one |
--app-origin URL | Public app origin; default http://localhost:3000 |
--upstream URL | Private backend; default http://127.0.0.1:3001 |
--client-id ID | Registered app ID |
--directory PATH | Target project; default current directory |
--yes | Disable prompts; provide credentials through environment/configuration arguments |
--dry-run | Show the files that would change without writing them |
--help | Print command help |
The command also accepts npx awwwth init. For noninteractive use, provide AWWWTH_CLIENT_SECRET through a secure environment mechanism. AWWWTH_ISSUER, AWWWTH_API_ORIGIN, and AWWWTH_CLIENT_ID can also supply their corresponding values. Secrets are never accepted as command-line arguments.
Gateway environment
These settings belong to the consuming application, not the identity server's .env:
| Variable | Meaning |
|---|---|
AWWWTH_ISSUER | Canonical identity-server origin, without a trailing slash |
AWWWTH_API_ORIGIN | OAuth API origin; defaults to issuer |
AWWWTH_APP_ORIGIN | Canonical public application origin |
AWWWTH_UPSTREAM | Private HTTP/HTTPS backend origin, without credentials or a path |
AWWWTH_CLIENT_ID | This app's registered client ID |
AWWWTH_CLIENT_SECRET | This app's private client secret |
AWWWTH_SESSION_SECRET | Canonical base64url encoding of 32 random bytes; generated by the CLI |
AWWWTH_HOST | Gateway listening interface; default 127.0.0.1 |
AWWWTH_PORT | Listening port; defaults to the app origin's explicit port, otherwise 3000 |
Use different client IDs and session secrets for different apps. Replicas of the same app need the same stable session secret. Keep credentials on the server and out of browser bundles and version control.
Read the authenticated user
The gateway replaces caller-supplied identity headers with verified values:
| Header | Value |
|---|---|
X-Awwwth-Sub | Stable user UUID |
X-Awwwth-User | Base64url-encoded JSON with sub, name, email, email_verified, and optional tenant_id |
In a Node HTTP backend, decode the user from a request that arrived through the gateway:
const encodedUser = request.headers['x-awwwth-user'];
if (typeof encodedUser !== 'string') {
response.writeHead(401);
response.end('Authentication required');
return;
}
const user = JSON.parse(Buffer.from(encodedUser, 'base64url').toString('utf8'));
Other languages can decode the same header using their base64url and JSON libraries. Only trust these headers when the backend is inaccessible directly from the public network. Use sub for user-owned resource checks; authentication does not replace your application's authorization rules.
Frontend code can read the user without handling OAuth tokens:
export async function currentUser() {
const response = await fetch('/auth/awwwth/session', {
credentials: 'same-origin',
headers: { Accept: 'application/json' },
});
if (response.status === 401) {
const returnTo = location.pathname + location.search;
location.assign(`/auth/awwwth/login?${new URLSearchParams({ returnTo })}`);
return null;
}
if (!response.ok) throw new Error('Sign-in is temporarily unavailable. Please retry.');
const result = await response.json();
return result.user;
}
Show a loading state while this function runs, handle a thrown error with a retry action, and use textContent or framework text interpolation when rendering profile fields.
Gateway endpoints
| Method and path | Result |
|---|---|
GET /auth/awwwth/login | Starts authorization; accepts an optional local returnTo path |
GET /auth/awwwth/signup | Opens Awwwth-hosted account creation for this app's directory |
GET /auth/awwwth/forgot | Opens Awwwth-hosted password recovery |
GET /auth/awwwth/widget.js | Serves the first-party frontend widget |
GET /auth/awwwth/widget.css | Serves its responsive styles |
GET /auth/awwwth/callback | Verifies callback state and issuer, exchanges the code, validates the ID token, and sets the app cookie |
GET /auth/awwwth/session | Returns {user, login} with 200, or {user: null, login} with 401 |
POST /auth/awwwth/logout | Revokes this app's grant and clears its cookie; returns an account-site URL |
All other upstream routes require authentication. A GET accepting text/html redirects to sign-in when needed; API requests receive 401 with a login URL. Browser navigation must perform the redirect; fetching the cross-origin authorization endpoint through AJAX is not the login flow.
App mutations require an Origin header exactly matching AWWWTH_APP_ORIGIN. OAuth credentials and tokens are not returned in the user endpoint or identity headers. See shared sign-in for expiry, revocation, and logout behavior.
Production deployment
- Deploy the updated identity server and register the app's production callback.
- Run the application's production backend on a private interface or network.
- Run the generated gateway as a separately supervised Node process.
- Configure public HTTPS to forward to the gateway and preserve the public app Host header.
- Supply production environment values and keep the same session secret across app replicas.
- Verify sign-in through the public URL and confirm direct public backend access is blocked.
For example, with AWWWTH_APP_ORIGIN=https://app.example.com, the gateway defaults to listening on port 3000. Your HTTPS reverse proxy should forward to that listener; the backend could listen privately on port 3001. The gateway itself does not terminate TLS.
Backend responses are marked private, no-store. Every authenticated request introspects its access token, so identity-server availability and capacity affect application traffic.
Limits and troubleshooting
| Symptom or limitation | Explanation or action |
|---|---|
| Callback rejected | Register the exact scheme, hostname, port, and /auth/awwwth/callback path |
| Identity endpoint mismatch | Set AWWWTH_API_ORIGIN to the separate API origin advertised by discovery |
invalid_host | Reverse proxy must preserve the public app Host header |
invalid_origin | Mutations must originate from the configured public app origin |
authentication_unavailable / 503 | Check issuer availability, client credentials, API origin, and token verification configuration |
| Backend unavailable / 502 | Start the backend and verify the private upstream address |
| Login repeats or wrong directory is used | Use the same issuer and directory for both apps and the same browser profile |
| API returns 401 after several minutes | Navigate to login to renew via the central session; this version does not refresh tokens in the background |
| Setup refuses to overwrite files | Review the existing installation; the CLI intentionally preserves it |
| Hot reload / WebSockets fail | WebSocket upgrades are unsupported; use a production build |
| Webhooks or public pages need anonymous access | This gateway has no public-route exception mechanism |
| Static-only hosting | A Node gateway process and reachable HTTP backend are required |
| Long-lived upstream connection closes | The gateway applies a 30-second upstream idle timeout |
For a walkthrough with two independently configured applications, continue to Shared sign-in.