With Authentication
This example demonstrates setting up Cobalt Auth with various authentication providers.
Email Code Provider
The simplest authentication method - users receive a code via email.
Auth Configuration
typescript
// server/auth.ts
import auth from "@cobalt27/auth";
import { string } from "valibot";
import { CodeUI } from "@openauthjs/openauth/ui/code";
import { MemoryStorage } from "@openauthjs/openauth/storage/memory";
import { CodeProvider } from "@openauthjs/openauth/provider/code";
export default {
clientId: "my-app",
issuer: {
cobalt: auth({
models: {
user: {
id: "String @id @default(ulid())",
},
},
tokens: {
user: {
id: string(),
email: string(),
},
},
providers: {
code: CodeProvider<{ email: string }>(
CodeUI({
mode: "email",
sendCode: async (email, code) => {
// In development, log to console
console.log(`Login code for ${email}: ${code}`);
// In production, send email
// await sendEmail({
// to: email,
// subject: "Your login code",
// body: `Your code is: ${code}`
// });
},
}),
),
},
openauth: (sdk) => ({
storage: MemoryStorage({
persist: "./persist.json",
}),
success: async (ctx, value) => {
if (value.provider === "code") {
const email = value.claims.email;
const user = await sdk.mutation.adminAuthSignIn({
user_id: email,
claims: { email },
provider: "email",
})(({ id }) => ({ id }));
if (!user?.id) {
throw new Error("User not found");
}
return ctx.subject("user", {
id: user.id,
email
});
}
throw new Error("Invalid provider");
},
}),
}),
},
};OAuth Providers
Google OAuth
typescript
// server/auth.ts
import auth from "@cobalt27/auth";
import { string } from "valibot";
import { GoogleProvider } from "@openauthjs/openauth/provider/google";
import { MemoryStorage } from "@openauthjs/openauth/storage/memory";
export default {
clientId: "my-app",
issuer: {
cobalt: auth({
models: {
user: {
id: "String @id @default(ulid())",
googleId: "String? @unique",
},
},
tokens: {
user: {
id: string(),
email: string(),
name: string(),
},
},
providers: {
google: GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
scopes: ["email", "profile"],
}),
},
openauth: (sdk) => ({
storage: MemoryStorage({
persist: "./persist.json",
}),
success: async (ctx, value) => {
if (value.provider === "google") {
const { email, name, sub } = value.claims;
const user = await sdk.mutation.adminAuthSignIn({
user_id: sub,
claims: { email, name },
provider: "google",
})(({ id }) => ({ id }));
return ctx.subject("user", {
id: user!.id,
email,
name
});
}
throw new Error("Invalid provider");
},
}),
}),
},
};GitHub OAuth
typescript
import { GithubProvider } from "@openauthjs/openauth/provider/github";
providers: {
github: GithubProvider({
clientId: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
scopes: ["user:email"],
}),
}Multiple Providers
typescript
providers: {
code: CodeProvider<{ email: string }>(
CodeUI({
mode: "email",
sendCode: async (email, code) => {
console.log(`Code for ${email}: ${code}`);
},
}),
),
google: GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
}),
github: GithubProvider({
clientId: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
}),
},
openauth: (sdk) => ({
storage: MemoryStorage({ persist: "./persist.json" }),
success: async (ctx, value) => {
let email: string;
let userId: string;
switch (value.provider) {
case "code":
email = value.claims.email;
userId = email;
break;
case "google":
email = value.claims.email;
userId = value.claims.sub;
break;
case "github":
email = value.claims.email;
userId = value.claims.id;
break;
default:
throw new Error("Unknown provider");
}
const user = await sdk.mutation.adminAuthSignIn({
user_id: userId,
claims: { email },
provider: value.provider,
})(({ id }) => ({ id }));
return ctx.subject("user", { id: user!.id, email });
},
}),Frontend Integration
React Router 7 Root
typescript
// app/root.tsx
import { redirect, Outlet } from "react-router";
import sdk from "sdk";
import {
makeAuthLoader,
accessTokenFromCookie,
} from "@cobalt27/auth/react/rr7";
// Initialize SDK with cookie-based auth
sdk.init({
auth: accessTokenFromCookie,
});
// Create auth-protected loader
export const loader = makeAuthLoader(
{
clientID: "my-app",
issuer: "http://localhost:4000",
unprotectedPaths: ["/login", "/signup", "/error", "/logout"],
},
// On successful auth
(tokens) => {
sdk.init({
auth: tokens.tokens.access,
});
},
// On auth error
(error) => {
console.error("Auth error:", error);
return redirect("/error?error=" + encodeURIComponent(error));
}
);
export default function Root() {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<Outlet />
</body>
</html>
);
}Error Route
typescript
// app/routes/error.tsx
import { useSearchParams, Link } from "react-router-dom";
export default function ErrorPage() {
const [searchParams] = useSearchParams();
const error = searchParams.get("error");
return (
<div className="min-h-screen flex items-center justify-center">
<div className="bg-white p-8 rounded-lg shadow-md max-w-md">
<h1 className="text-2xl font-bold text-red-600 mb-4">
Authentication Error
</h1>
<p className="text-gray-600 mb-6">
{error || "An unknown error occurred"}
</p>
<Link
to="/"
className="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600"
>
Try Again
</Link>
</div>
</div>
);
}Logout Route
typescript
// app/routes/logout.tsx
import { redirect } from "react-router";
export const loader = async () => {
return redirect("/", {
headers: {
"Set-Cookie": [
"access_token=; Path=/; HttpOnly; Max-Age=0; SameSite=Lax",
"refresh_token=; Path=/; HttpOnly; Max-Age=0; SameSite=Lax",
].join(", "),
},
});
};
export default function Logout() {
return (
<div className="min-h-screen flex items-center justify-center">
<p>Logging out...</p>
</div>
);
}Using Auth in Operations
Basic Auth Check
typescript
// server/operations/profile.ts
export function Query() {
const { token } = $$auth(this);
const { id, email } = token.subject.properties;
return {
id,
email,
displayName: email.split("@")[0],
};
}
export const __typename = "UserProfile";Protected Data Access
typescript
// server/operations/myTodos.ts
export async function Query() {
const { prisma } = $$ctx(this);
const { token } = $$auth(this);
const userId = token.subject.properties.id;
return prisma.todo.findMany({
where: { ownerId: userId },
orderBy: { createdAt: "desc" },
});
}Role-Based Access
typescript
// server/operations/adminUsers.ts
export async function Query() {
const { token } = $$auth(this);
const { role } = token.subject.properties;
if (role !== "admin") {
throw new Error("Admin access required");
}
const { prisma } = $$ctx(this);
return prisma.user.findMany();
}Storage Options
Memory Storage (Development)
typescript
import { MemoryStorage } from "@openauthjs/openauth/storage/memory";
openauth: (sdk) => ({
storage: MemoryStorage({
persist: "./auth-data.json", // Persists to file
}),
// ...
})DynamoDB Storage (Production)
typescript
import { DynamoStorage } from "@openauthjs/openauth/storage/dynamo";
openauth: (sdk) => ({
storage: DynamoStorage({
table: "auth-tokens",
}),
// ...
})Redis Storage (Production)
typescript
import { RedisStorage } from "@openauthjs/openauth/storage/redis";
openauth: (sdk) => ({
storage: RedisStorage({
url: process.env.REDIS_URL,
}),
// ...
})Environment Variables
bash
# .env
# OAuth Providers
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret
GITHUB_CLIENT_ID=your-github-client-id
GITHUB_CLIENT_SECRET=your-github-client-secret
# Storage (Production)
REDIS_URL=redis://localhost:6379
# Email Service (Production)
SMTP_HOST=smtp.example.com
SMTP_USER=user
SMTP_PASS=passwordSecurity Best Practices
- Use HTTPS in production - Never send tokens over HTTP
- Set secure cookie flags - HttpOnly, Secure, SameSite
- Validate token on every request -
$$auththrows if invalid - Use short token expiration - Balance security vs UX
- Implement refresh tokens - For longer sessions
- Log auth events - For security monitoring
Next Steps
- Todo App — Complete example app
- Cobalt Auth — Full auth documentation
- Context Factory — Context setup