Skip to content

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.


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.


With a local auth server in Docker:

Terminal window
npx seamless-cli add --local --email=you@example.com

With a managed application (needs seamless login first):

Terminal window
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:

Terminal window
npx seamless-cli add --local --yes --email=you@example.com < /dev/null

add 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):

Terminal window
npx seamless-cli add --local --email=you@example.com --api-url=http://localhost:4000

The 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.


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.com
Wrote 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_CONSOLE
Wrote 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-dom

Then 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).

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).

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.

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.

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.


add prints this code; adding it is up to you.

  1. Start the auth server (local only): docker compose -f seamless/docker-compose.yml up -d.
  2. Load the .env. The printed Python code loads it with load_dotenv() (python-dotenv is in the install). The Node, Go, and Rust code reads process.env, os.Getenv, or std::env::var; if your backend does not load .env already, add tells you how for its language.
  3. Paste the backend code into the entry file it names, after you create the app and before your routes, merging the imports with yours.
  4. Wire the web app with the printed code for each file.
  5. 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) or APP_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.


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.

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.

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.

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.


  • React gets two steps. First, wrap the app in BrowserRouter and AuthProvider with apiHost read from the API URL variable (keep your router if you have one). Second, add the bundled sign-in screens: AuthRoutes as the catch-all route for signed-out visitors, and a RequireAuth wrapper that sends everyone else to /login. Read the session with useAuth(). 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 the HttpClient interceptor for your providers (src/app/app.config.ts when it exists), then the bundled sign-in screens as routes in src/app/app.routes.ts with 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.ts or .js), a root layout that turns off server rendering (export const ssr = false) and shares the session, one page per sign-in screen, and requireAuth and requireGuest loads (Svelte SDK).