Add Seamless Auth To An Existing App
seamless add adds Seamless Auth to a project you already have. Run it at the root of the
repository: it finds your backend and your web app, connects an auth server (a local one in Docker,
or a managed application), writes the environment values your backend needs, installs the server
adapter and the web SDK, and prints the code to add, file by file. It never edits your source files,
so the last step is always yours.
Use it when you have a working backend and want sign-in added to it. To start from nothing, use
seamless init instead, which scaffolds a whole project with auth already
wired.
seamless add shipped in seamless-cli 0.22.0. Everything on this page is from seamless-cli 0.23.1.
The CLI Command Reference lists every flag.
What It Supports
Section titled “What It Supports”| Backend | Found from | Adapter installed |
|---|---|---|
| Express, Fastify | express or fastify in package.json |
@seamless-auth/express or @seamless-auth/fastify |
| Go: net/http, Gin, chi, Echo | go.mod |
github.com/fells-code/seamless-auth-go |
| Rust: Axum | axum in Cargo.toml |
seamless-auth (crates.io) and tower-http with the cors feature |
| Python: FastAPI, Django | pyproject.toml or requirements.txt |
seamless-auth[fastapi] or seamless-auth[django] (PyPI), python-dotenv, and django-cors-headers for Django |
| Web app | Binding installed |
|---|---|
| React | @seamless-auth/react, and react-router-dom when missing |
| Angular | @seamless-auth/angular |
| Vue | @seamless-auth/vue |
| SvelteKit | @seamless-auth/svelte |
It looks at the repository root and at api/, server/, backend/, web/, client/,
frontend/, app/, apps/*, and packages/*, and uses the first backend and the first web app
it finds. The web app is optional: a backend on its own is wired without one.
Run It
Section titled “Run It”With a local auth server in Docker:
npx seamless-cli add --local --email=you@example.comWith a managed application (needs seamless login first):
npx seamless-cli add --app <application-id>--email is the owner address: registering with it makes you the first admin of the local auth
server. Without --local or --app, add asks which one you want. With --yes, or with no
terminal attached, it never waits for input and fails instead, naming the flag to pass, so this
form is safe in scripts and for coding agents:
npx seamless-cli add --local --yes --email=you@example.com < /dev/nulladd works out your backend’s origin as http://localhost:<port>: the PORT in the backend’s
.env, else the port in the entry file’s listen call, else 3000 for Node, 8000 for FastAPI and
Django (the uvicorn and runserver defaults), and 8080 for Go and Rust. Pass --api-url when that
is wrong, and --web-url when your web app is not on its framework’s default
(http://localhost:5173, 4200 for Angular, 3001 for Create React App):
npx seamless-cli add --local --email=you@example.com --api-url=http://localhost:4000The origins matter: the web app’s API URL points at --api-url, and the local auth server only
accepts passkey ceremonies from the origins it is given.
What It Changes
Section titled “What It Changes”For an Express API in api/ and a React app in web/, a run with --local reports:
seamless add
Backend: Express in api (src/server.ts) Web: React in web (src/main.tsx)Owner email: you@example.comWrote seamless/: docker-compose.yml (Compose project my-app-seamless), .env, .gitignore
Wrote api/.env: AUTH_SERVER_URL, AUTH_SERVER_ISSUER, API_SERVICE_TOKEN, JWKS_KID, COOKIE_SIGNING_KEY, UI_ORIGINS, SERVE_ADMIN_CONSOLEWrote web/.env.local: VITE_API_URL
$ cd api && npm install @seamless-auth/express cookie-parser cors$ cd api && npm install --save-dev @types/cookie-parser @types/cors$ cd web && npm install @seamless-auth/react react-router-domThen it prints numbered steps: start the auth server, add the backend code, wire the web app, and
sign in. With --skip-install, the install commands come first, as step 1.
The project name in that output comes from the repository folder (my-app here).
seamless/ (with --local)
Section titled “seamless/ (with --local)”| File | What it holds |
|---|---|
seamless/docker-compose.yml |
Postgres and the auth API image the CLI pins, under the Compose project <repo>-seamless, so its containers and volumes (<repo>-seamless_pgdata, <repo>-seamless_auth-keys) belong to this repository alone. The auth server listens on 127.0.0.1:5312 only; Postgres is not published |
seamless/.env |
The auth server’s configuration and secrets: OWNER_EMAIL, ORIGINS (your web origin, plus your backend origin when it serves the console), APP_ORIGINS (your backend origin) |
seamless/.gitignore |
Keeps seamless/.env out of the repository |
Nothing else at the repository root changes, so the stack is one folder to delete. Running add --local again asks before replacing seamless/, because a new stack issues new secrets; under
--yes it needs --force. A replaced stack keeps its project name, including one you edited, and
writes the new API_SERVICE_TOKEN into your backend’s .env too.
With --app, no seamless/ folder is written. add issues a service token for the application,
and asks first when one already exists, because the new token invalidates it (--yes needs
--force to rotate it).
Your backend’s .env
Section titled “Your backend’s .env”| Key | Value | Overwrites yours? |
|---|---|---|
AUTH_SERVER_URL |
http://localhost:5312, or the managed instance URL |
Yes |
AUTH_SERVER_ISSUER |
http://auth:5312 (the issuer the Docker stack signs as), or the managed instance URL |
Yes |
API_SERVICE_TOKEN |
The service token shared with the auth server | Yes |
JWKS_KID |
The key id the adapter puts on its service tokens (the auth API does not check it) | Yes |
COOKIE_SIGNING_KEY |
A generated secret that signs the session cookies | No |
UI_ORIGINS |
Your web origin, for CORS | No |
SERVE_ADMIN_CONSOLE |
true with --local (unless --admin=none), otherwise false |
No |
The file is created if it does not exist. Every other line, comment, and key in it is kept, and new
keys are appended under a # Seamless Auth comment. When the .env does not look gitignored,
add says so; add it to .gitignore yourself.
Your web app’s .env.local
Section titled “Your web app’s .env.local”React (Vite), Vue, and SvelteKit get VITE_API_URL, and Create React App gets
REACT_APP_API_URL, set to your backend origin. An existing value is kept. Angular gets nothing
written, because the Angular CLI does not read .env: the URL is in the printed config instead.
Packages
Section titled “Packages”Node projects install with npm, pnpm, yarn, or bun, whichever the nearest lock file names. Go uses
go get, Rust cargo add, and Python uv add or poetry add when the project uses one of them.
A Python project with only pip gets the pip install printed instead of run, since only you know
which environment it belongs in. Apps without a router also get one: react-router-dom,
@angular/router, or vue-router. --skip-install prints every command instead of running it, and a command that
fails is printed for you to run.
What You Still Do By Hand
Section titled “What You Still Do By Hand”add prints this code; adding it is up to you.
- Start the auth server (local only):
docker compose -f seamless/docker-compose.yml up -d. - Load the
.env. The printed Python code loads it withload_dotenv()(python-dotenv is in the install). The Node, Go, and Rust code readsprocess.env,os.Getenv, orstd::env::var; if your backend does not load.envalready,addtells you how for its language. - Paste the backend code into the entry file it names, after you create the app and before your routes, merging the imports with yours.
- Wire the web app with the printed code for each file.
- Replace the development delivery before deploying. The printed backend code logs one-time
codes and magic links to the console instead of sending them, so you can sign in without a mail
or SMS provider. It is off when
NODE_ENV=production(Node) orAPP_ENV=production(Go, Rust, Python).
Then start your app, open the web origin, and register with the owner email. The one-time code
prints in your backend’s console. With --local, the admin dashboard is at /console on your
backend once you add the console lines.
Per Backend
Section titled “Per Backend”Express and Fastify
Section titled “Express and Fastify”add installs @seamless-auth/express, cookie-parser, and cors (with their @types/* as dev
dependencies when the project is TypeScript), or @seamless-auth/fastify and @fastify/cors. The
printed code mounts the adapter at /auth, adds credentialed CORS for UI_ORIGINS, mounts the
console proxy at /console behind SERVE_ADMIN_CONSOLE, and protects an example GET /api/me
with requireAuth. It is TypeScript or JavaScript to match the project.
The Express code uses your existing app and express. @seamless-auth/express needs Express 5.
For what each line does, and a complete server to compare against, see
Add Passwordless Auth To An Express API.
To load the .env, start Node with node --env-file=.env (Node 20.6+) or import "dotenv/config"
first.
add runs go get github.com/fells-code/seamless-auth-go@latest and picks net/http, Gin, chi, or
Echo from go.mod. It names the entry as main.go, server.go, or cmd/*/main.go, whichever
exists. The printed code builds seamlessauth.Options from the environment, mounts
auth.Handler() at /auth and auth.ConsoleHandler() at /console, and protects GET /api/me
with auth.RequireAuth.
The printed code also adds credentialed CORS for the origins in UI_ORIGINS: a small middleware
for net/http and chi (serve the mux through it where you call ListenAndServe), the Gin starter’s
middleware for Gin, and middleware.CORSWithConfig for Echo.
By hand: load the .env, for example with github.com/joho/godotenv. See the
Go adapter.
Rust (Axum)
Section titled “Rust (Axum)”add runs cargo add seamless-auth and cargo add tower-http --features cors. The printed code
for src/main.rs builds the adapter with Adapter::builder, nests auth.router() at /auth,
protects /api/me with auth.require_auth(), adds a credentialed CorsLayer for UI_ORIGINS
that leaves out the console, merges auth.console_router() after it, and serves with connect info
so the adapter can forward the client address.
By hand: load the .env with the dotenvy crate. See the Rust adapter.
FastAPI
Section titled “FastAPI”add installs seamless-auth[fastapi]>=0.2 and python-dotenv with uv add or poetry add, or
prints the pip install for a pip project. It names the entry as the first of main.py, app/main.py,
app.py, src/main.py, or api/main.py that exists. The printed code creates an Adapter from
the .env (loaded with load_dotenv() first), adds CORSMiddleware for UI_ORIGINS with credentials, includes auth_router
(and console_router behind SERVE_ADMIN_CONSOLE), and protects GET /api/me with
RequireUser.
By hand: with pip, add the packages to requirements.txt yourself. See the
Python adapter.
Django
Section titled “Django”add installs seamless-auth[django]>=0.2, python-dotenv, and django-cors-headers the same
way, and names the settings module that manage.py points at. The printed code loads the .env
with load_dotenv() at the top of settings.py, adds corsheaders with credentialed CORS for
UI_ORIGINS, adds a SEAMLESS_AUTH dict, includes seamless_auth.django at auth/ (and the
console at console/) in urls.py, and protects a view with @require_auth.
By hand: with pip, add the packages to requirements.txt yourself. See the
Python adapter.
Per Web App
Section titled “Per Web App”- React gets two steps. First, wrap the app in
BrowserRouterandAuthProviderwithapiHostread from the API URL variable (keep your router if you have one). Second, add the bundled sign-in screens:AuthRoutesas the catch-all route for signed-out visitors, and aRequireAuthwrapper that sends everyone else to/login. Read the session withuseAuth(). To use your own screens instead, see How To Build Your Own Auth UI (React SDK). - Angular gets
provideSeamlessAuth(with the backend URL in it) and theHttpClientinterceptor for your providers (src/app/app.config.tswhen it exists), then the bundled sign-in screens as routes insrc/app/app.routes.tswith guards for signed-in and signed-out pages (Angular SDK). - Vue gets the plugin installed after your router (or a new router when the app has none), the sign-in screens as routes, and route guards (Vue SDK).
- SvelteKit gets the session module (
src/lib/auth.tsor.js), a root layout that turns off server rendering (export const ssr = false) and shares the session, one page per sign-in screen, andrequireAuthandrequireGuestloads (Svelte SDK).