Drop-in authentication and identity for modern web frameworks.
A curated monorepo of framework-native libraries that make it effortless to add secure sessions, user management, and auth UI to your React, Next.js, Remix, TanStack Start, Vue, Astro, SvelteKit, Angular, React Native, and Node (Express / Fastify) applications β plus backend SDKs for Python (FastAPI, Flask, Django, Starlette, aiohttp), Go (Gin), Rust (axum, actix-web, Rocket, warp, poem), and Kotlin (Ktor) that speak the same session protocol.
Quick Start Β· Packages Β· Examples Β· Development Β· Contributing
- π§© Framework-native β idiomatic packages for React, Next.js (App Router), Remix, TanStack Start, Vue, Astro, SvelteKit, Angular, React Native, and Node backends (Express / Fastify), plus Python (FastAPI, Flask, Django, Starlette, aiohttp), Go (Gin), Rust (axum, actix-web, Rocket, warp, poem), and Kotlin (Ktor) backends. No glue code.
- π Polyglot backends β Node, Python, Go, Rust, and Kotlin services share one
authdog-sessioncookie, one OIDCuserinfoflow, and one trusted identity-host allowlist, so a single Authdog environment works across your whole stack. - π Secure by default β token validation, cookie handling, and session lifecycle managed for you.
- π¨ Batteries-included UI β ready-made, accessible components (sign-in, user profile, TOTP, navbar) you can drop in or restyle.
- β‘ Tiny & tree-shakeable β ESM-first,
sideEffects: false, dual CJS/ESM builds via tsup. - π¦ Type-safe end to end β written in TypeScript with first-class types shipped in every package.
- π Server & client split β explicit
/clientand/serverentry points keep secrets server-side.
| Package | Version | Description | CI |
|---|---|---|---|
@authdog/react-elements |
React UI components | ||
@authdog/nextjs-app |
Next.js App Router SDK | ||
@authdog/remix-node |
Remix SDK | ||
@authdog/tanstack-start |
TanStack Start SDK | ||
@authdog/vue |
Vue SDK | ||
@authdog/astro |
Astro SDK | ||
@authdog/sveltekit |
SvelteKit SDK | ||
@authdog/angular |
Angular SDK | ||
@authdog/express |
Express SDK | ||
@authdog/fastify |
Fastify SDK | ||
@authdog/react-native |
React Native / Expo SDK | ||
@authdog/node-commons |
Shared Node utilities |
| Package | Registry | Description | CI |
|---|---|---|---|
authdog-fastapi |
PyPI | Python SDK β FastAPI, Flask, Django, Starlette, aiohttp | |
authdog (Go) |
github.com/authdog/web-sdk/packages/go |
Go SDK with Gin middleware | |
authdog-axum & friends |
crates.io | Rust workspace β Axum, Actix, Rocket, Warp, Poem | |
authdog-ktor |
Maven Central | Kotlin / Ktor SDK |
These backend SDKs mirror the Node @authdog/express / @authdog/fastify
packages on the wire (same authdog-session cookie, same userinfo flow, same
identity-host allowlist), so they validate sessions issued for the same Authdog
environment.
Pick the package for your framework and install it with your favorite package manager:
# Next.js (App Router)
bun add @authdog/nextjs-app @authdog/react-elements
# Remix
bun add @authdog/remix-node @authdog/react-elements
# TanStack Start
bun add @authdog/tanstack-start @authdog/react-elements
# Vue
bun add @authdog/vue
# Astro
bun add @authdog/astro
# SvelteKit
bun add @authdog/sveltekit
# Angular
bun add @authdog/angular
# Express (backend)
bun add @authdog/express
# Fastify (backend)
bun add @authdog/fastify
# React Native / Expo
bun add @authdog/react-nativeBackend SDKs for other languages install with their native package managers:
# Python β one install, pick your framework's extra
pip install "authdog-fastapi[fastapi]" # or [flask], [django], [starlette], [aiohttp]
# Go (Gin)
go get github.com/authdog/web-sdk/packages/go@latest
# Rust β add the crate for your framework to Cargo.toml
cargo add authdog-axum # or authdog-actix / authdog-rocket / authdog-warp / authdog-poem
# Kotlin (Ktor) β add to build.gradle.kts
# implementation("com.authdog:authdog-ktor:0.1.0")Provide your Authdog public key (pk_β¦). Each framework reads it from a different
place β use the variable that matches your package:
| Framework | Variable | Where it's read |
|---|---|---|
| Next.js (client) | NEXT_PUBLIC_PK_AUTHDOG |
Exposed to the browser by Next.js |
| Next.js (server) | PK_AUTHDOG |
Server-only (logout, etc.) |
| Remix | PK_AUTHDOG |
Loaders / actions |
| TanStack Start | PK_AUTHDOG |
Server functions / route loaders |
| Express (backend) | PK_AUTHDOG |
createAuthdog({ publicKey }) |
| Fastify (backend) | AUTHDOG_PK |
authdogPlugin({ publicKey }) |
| Vue | VITE_AUTHDOG_PUBLIC_KEY |
Vite-exposed (use the VITE_ prefix) |
| Astro | PUBLIC_AUTHDOG_PUBLIC_KEY |
Exposed via Astro's PUBLIC_ prefix |
| SvelteKit | PUBLIC_AUTHDOG_PUBLIC_KEY |
Exposed via SvelteKit's PUBLIC_ prefix |
| React Native / Expo | EXPO_PUBLIC_PK_AUTHDOG |
Exposed to the app by Expo |
| Angular | β | Passed directly to provideAuthdog(...) |
| Python (all) | PK_AUTHDOG |
Authdog(public_key=...) |
| Go (Gin) | PK_AUTHDOG |
authdog.New(authdog.Config{PublicKey}) |
| Rust (all) | PK_AUTHDOG |
Authdog::new(...) |
| Kotlin (Ktor) | PK_AUTHDOG |
Authdog(System.getenv("PK_AUTHDOG")) |
# Next.js (App Router)
NEXT_PUBLIC_PK_AUTHDOG=pk_xxxxxxxxxxxxxxxxYour public key (
pk_β¦) is available in the Authdog dashboard. It is safe to expose to the browser. Your secret key (sk_β¦), used only by backend packages, must never be committed or shipped to the client.
Wrap your app with the provider in app/layout.tsx:
import "@authdog/react-elements/styles.css";
import { AuthdogProvider } from "@authdog/nextjs-app/client";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<AuthdogProvider>{children}</AuthdogProvider>
</body>
</html>
);
}Read the current user from any client component:
"use client";
import { useUser } from "@authdog/nextjs-app";
import { UserProfile } from "@authdog/react-elements";
export default function Profile() {
const { user, isLoading } = useUser();
return <UserProfile loading={isLoading} user={user} />;
}<script setup lang="ts">
import { AuthdogProvider } from "@authdog/vue/client";
import { useUser } from "@authdog/vue";
const { user, isLoading } = useUser();
</script>
<template>
<AuthdogProvider>
<p v-if="isLoading">Loadingβ¦</p>
<p v-else>Welcome, {{ user?.name }}</p>
</AuthdogProvider>
</template>Wire the middleware once (SSR mode), then read the session anywhere via
Astro.locals.authdog:
// src/middleware.ts
import { defineMiddleware } from "astro:middleware";
import { authdogMiddleware } from "@authdog/astro/server";
export const onRequest = defineMiddleware(
authdogMiddleware({ publicKey: import.meta.env.PUBLIC_AUTHDOG_PUBLIC_KEY }),
);---
// src/pages/profile.astro
import { createAuthdogServer } from "@authdog/astro/server";
const authdog = createAuthdogServer({ publicKey: import.meta.env.PUBLIC_AUTHDOG_PUBLIC_KEY });
const user = Astro.locals.authdog.isAuthenticated ? await authdog.getUser(Astro.request) : null;
---
{user ? <p>Welcome, {user.user?.displayName}</p> : <a href="/login">Sign in</a>}Wire the handle hook once, then read the session anywhere via
event.locals.authdog:
// src/hooks.server.ts
import { createAuthdogHandle } from "@authdog/sveltekit/server";
export const handle = createAuthdogHandle({
publicKey: import.meta.env.PUBLIC_AUTHDOG_PUBLIC_KEY,
});// src/routes/profile/+page.server.ts
import { createAuthdogServer } from "@authdog/sveltekit/server";
import type { PageServerLoad } from "./$types";
const authdog = createAuthdogServer({ publicKey: import.meta.env.PUBLIC_AUTHDOG_PUBLIC_KEY });
export const load: PageServerLoad = async ({ request, locals }) => {
const user = locals.authdog.isAuthenticated ? await authdog.getUser(request) : null;
return { user: user?.user ?? null };
};// app/routes/_index.tsx
import { remixAuthLoader } from "@authdog/remix-node";
export const loader = remixAuthLoader;Resolve the session from a server function (TanStack Start speaks the Web Fetch
API, so the loader returns a standard Response):
// app/routes/index.tsx
import { createServerFn } from "@tanstack/react-start";
import { getWebRequest } from "@tanstack/react-start/server";
import { identityLoader } from "@authdog/tanstack-start";
export const loadIdentity = createServerFn({ method: "GET" }).handler(async () => {
const response = await identityLoader()({ request: getWebRequest() });
return response.json();
});Wrap your app with the provider (strips the ?token=β¦ from the URL after the
server persists the session):
import { AuthdogProvider } from "@authdog/tanstack-start/client";
<AuthdogProvider>{children}</AuthdogProvider>;Register the SDK (standalone, app.config.ts) and add the interceptor to your HttpClient:
import { provideHttpClient, withInterceptors } from "@angular/common/http";
import { provideAuthdog, authdogInterceptor } from "@authdog/angular";
export const appConfig = {
providers: [
provideAuthdog({ publicKey: "pk_xxxxxxxxxxxxxxxx" }),
provideHttpClient(withInterceptors([authdogInterceptor])),
],
};Read auth state from the signals-based AuthdogService:
import { Component, inject } from "@angular/core";
import { AuthdogService } from "@authdog/angular";
@Component({ /* β¦ */ })
export class ProfileComponent {
readonly auth = inject(AuthdogService);
// auth.isLoading(), auth.token(), auth.user() β signals
// auth.signIn(), auth.signUp(), auth.signOut(), auth.fetchUser()
}import express from "express";
import { createAuthdog } from "@authdog/express";
const app = express();
const authdog = createAuthdog({ publicKey: process.env.PK_AUTHDOG! });
app.use(authdog.attachSession()); // resolves `req.authdog` for every request
app.get("/me", authdog.requireAuth, (req, res) => res.json(req.authdog!.user));
app.get("/logout", authdog.logout);import Fastify from "fastify";
import { authdogPlugin } from "@authdog/fastify";
const app = Fastify();
await app.register(authdogPlugin, { publicKey: process.env.AUTHDOG_PK! });
// `request.authdog` is now available everywhere; `app.authdog.requireAuth`
// is the server-side enforcement point.
app.get("/me", { preHandler: app.authdog.requireAuth }, async (req) => req.authdog!.user);
app.get("/logout", (req, reply) => app.authdog.logout(req, reply));import os
from fastapi import Depends, FastAPI, Request
from authdog.fastapi import Authdog
app = FastAPI()
authdog = Authdog(public_key=os.environ["PK_AUTHDOG"])
@app.get("/me")
async def me(user=Depends(authdog.require_auth)): # require_auth is the gate
return user
@app.get("/logout")
async def logout(request: Request):
return authdog.logout(request)The same Authdog surface ships for other Python frameworks β import the
matching module: authdog.flask (@authdog.require_auth decorator),
authdog.django (middleware + decorator), authdog.starlette (ASGI
middleware), or authdog.aiohttp (@web.middleware). All share one
framework-agnostic core.
ad, _ := authdog.New(authdog.Config{PublicKey: os.Getenv("PK_AUTHDOG")})
r := gin.Default()
r.Use(ad.AttachSession()) // resolves *authdog.Context per request
r.GET("/me", ad.RequireAuth(), func(c *gin.Context) {
c.JSON(http.StatusOK, authdog.FromGin(c).User)
})
r.GET("/logout", ad.Logout)let authdog = Authdog::new(&std::env::var("PK_AUTHDOG").unwrap()).unwrap();
let app = Router::new()
.route("/me", get(me).layer(middleware::from_fn(require_auth)))
.route("/logout", get(logout))
.layer(middleware::from_fn_with_state(authdog.clone(), attach_session))
.with_state(authdog);
async fn me(ctx: AuthContext) -> Json<Value> { Json(ctx.user.unwrap_or(Value::Null)) }The same surface ships as a dedicated crate per framework on top of the shared
authdog-core: authdog-axum, authdog-actix (extractors + middleware),
authdog-rocket (request guards + catcher), authdog-warp (composable
filters), and authdog-poem (extractors + middleware).
import com.authdog.Authdog
fun Application.module() {
val authdog = Authdog(System.getenv("PK_AUTHDOG"))
routing {
get("/me") {
val ctx = authdog.requireAuth(call) ?: return@get // requireAuth is the gate
call.respondText(ctx.user.toString())
}
get("/logout") { authdog.logout(call) }
}
}Wrap your app with the provider, backed by hardware-secure storage, and handle the login deep link:
import * as SecureStore from "expo-secure-store";
import { AuthdogProvider, createSecureStoreAdapter } from "@authdog/react-native";
export default function App() {
return (
<AuthdogProvider
publicKey={process.env.EXPO_PUBLIC_PK_AUTHDOG!}
storage={createSecureStoreAdapter(SecureStore)}
>
<RootNavigator />
</AuthdogProvider>
);
}import { useSignIn, useRedirectHandler } from "@authdog/react-native";
// signIn("myapp://callback") opens Universal Login; handleRedirect(url)
// validates the returned token and persists it via your secure storage.Each framework ships a runnable demo under examples/. Set PK_AUTHDOG (or the framework's public-key variable) and start one with moon:
| Example | Package showcased | Run |
|---|---|---|
examples/nextjs-app |
@authdog/nextjs-app + @authdog/react-elements |
moon run nextjs-app-sample:dev |
examples/remix |
@authdog/remix-node + @authdog/react-elements |
moon run remix-playground:dev |
examples/vue-app |
@authdog/vue (home, login, signup, profile, permissions) |
moon run vue-app:dev |
examples/astro |
@authdog/astro (SSR middleware, server getUser, logout) |
moon run astro-app:dev |
examples/sveltekit |
@authdog/sveltekit (SSR handle hook, server getUser, logout) |
moon run sveltekit-app:dev |
examples/angular |
@authdog/angular (interceptor, guard, signals) |
moon run angular-app:dev |
examples/express |
@authdog/express (attachSession, requireAuth, logout) |
moon run express-api:dev |
examples/fastify |
@authdog/fastify (plugin, requireAuth, logout) |
moon run fastify-api:dev |
examples/react-native |
@authdog/react-native (Expo, deep-link sign-in, secure store) |
moon run react-native-app:dev |
This is a Bun + moon monorepo.
git clone https://github.com/authdog/web-sdk.git
cd web-sdk
bun installThe repo ships a Justfile for ergonomic shortcuts:
| Command | Description |
|---|---|
just build |
Build all publishable packages |
just dev-next |
Run the Next.js demo app |
just dev-remix |
Run the Remix demo app |
just ui |
Launch the Storybook component explorer |
just ci |
Run the full CI pipeline locally |
Prefer raw scripts? bun run build, bun run dev, bun run test, bun run lint, and bun run check-types all work via moon.
web-sdk/
βββ examples/ # Runnable demo apps (Next.js, Remix, Vue, Astro, SvelteKit, Angular, Express, Fastify, React Native)
βββ packages/ # Published SDK packages + shared configs
β βββ react-elements/ # React UI components
β βββ nextjs-app/ # Next.js App Router SDK
β βββ remix/ # Remix SDK (@authdog/remix-node)
β βββ tanstack-start/ # TanStack Start SDK
β βββ vue/ # Vue SDK
β βββ astro/ # Astro SDK
β βββ sveltekit/ # SvelteKit SDK
β βββ angular/ # Angular SDK
β βββ express/ # Express SDK
β βββ fastify/ # Fastify SDK
β βββ react-native/ # React Native / Expo SDK
β βββ node-commons/ # Shared Node utilities
β βββ python/ # Python SDK (FastAPI, Flask, Django, Starlette, aiohttp) β authdog-fastapi
β βββ go/ # Go SDK (Gin)
β βββ rust/ # Rust workspace (axum, actix, rocket, warp, poem) β authdog-core + crates
β βββ kotlin/ # Kotlin SDK (Ktor) β authdog-ktor
β βββ eslint/ # Shared ESLint config
β βββ typescript-config/# Shared tsconfig presets
βββ .moon/ # moon workspace & toolchain config
Versioning and changelogs are handled with Changesets:
bun run changeset # describe your change
bun run publish-packages # build + publish to npmContributions are welcome and appreciated! To get started:
- Fork the repository and create a feature branch.
- Make your changes and add a changeset (
bun run changeset). - Ensure
bun run cipasses. - Open a pull request describing your change.
Please report bugs and request features via GitHub Issues.
Found a vulnerability? Please do not open a public issue. See our Security Policy for responsible disclosure instructions.
MIT Β© Authdog