Adding Authentication
Add a users collection with login, JWT auth, and protected routes.
Authentication in Dyrected starts with an auth-enabled collection and a signing secret. From there you get login, JWT sessions (JWT is a JSON Web Token — a signed token the browser holds to prove who's logged in), and access rules you can apply to any collection or route. This guide sets all of that up and ends with protecting both your API and your frontend pages.
1. Add an auth collection
Turn any collection into an auth collection by setting auth: true. Dyrected adds the email and password fields for you, so you only declare the extra fields you want — here, a display name:
// dyrected.config.ts (same for both frameworks)
export default defineConfig({
collections: [
{
slug: 'users',
auth: true,
fields: [
{ name: 'name', label: 'Name', type: 'text' },
],
},
],
})2. Set the JWT secret
Dyrected signs session tokens with a secret you provide. Set it in your environment before anyone logs in:
# .env.local
DYRECTED_JWT_SECRET=a-long-random-secret-at-least-32-charsThe same secret must be set in every environment. Rotating it invalidates all existing sessions.
3. Log in
Logging in exchanges an email and password for a token and the user record. Store the token (a cookie is typical) and send it on future requests. Here it is in the plain SDK and wired into each framework:
import { createClient } from '@dyrected/sdk'
const client = createClient({ baseUrl: process.env.NEXT_PUBLIC_DYRECTED_URL! })
const { token, user } = await client.collection('users').login(
'[email protected]',
'hunter2'
)// app/login/actions.ts
'use server'
import { cookies } from 'next/headers'
import { createClient } from '@dyrected/sdk'
const client = createClient({ baseUrl: process.env.NEXT_PUBLIC_DYRECTED_URL! })
export async function login(email: string, password: string) {
const { token, user } = await client.collection('users').login(email, password)
cookies().set('dyrected-token', token, { httpOnly: true, path: '/' })
return user
}<!-- components/LoginForm.vue -->
<script setup lang="ts">
const { login, user, logout } = useDyrectedAuth('users')
async function submit(e: Event) {
const form = e.target as HTMLFormElement
await login(form.email.value, form.password.value)
}
</script>
<template>
<div v-if="user">
Hello, {{ user.name }} — <button @click="logout">Log out</button>
</div>
<form v-else @submit.prevent="submit">
<input name="email" type="email" placeholder="Email" />
<input name="password" type="password" placeholder="Password" />
<button type="submit">Log in</button>
</form>
</template>4. Protect API routes
With login working, restrict who can touch your data. Each collection takes an access block whose functions receive the signed-in user and return whether the action is allowed. This orders collection is readable and writable only by logged-in users, and each user can edit only their own orders:
// dyrected.config.ts — access functions work the same in both frameworks
{
slug: 'orders',
access: {
read: ({ user }) => !!user,
create: ({ user }) => !!user,
update: ({ user, doc }) => user?.sub === doc?.userId,
delete: ({ user }) => user?.roles?.includes('admin'),
},
}5. Protect frontend routes
Access rules guard your data, but you also want to keep signed-out visitors away from pages like a dashboard. Redirect them at the routing layer by checking for the token:
// middleware.ts
import { NextRequest, NextResponse } from 'next/server'
export function middleware(req: NextRequest) {
const token = req.cookies.get('dyrected-token')?.value
if (!token) {
return NextResponse.redirect(new URL('/login', req.url))
}
return NextResponse.next()
}
export const config = {
matcher: ['/dashboard/:path*', '/account/:path*'],
}// middleware/auth.ts
export default defineNuxtRouteMiddleware(() => {
const { user } = useDyrectedAuth('users')
if (!user.value) return navigateTo('/login')
})Apply it to a page:
<!-- pages/dashboard.vue -->
<script setup>
definePageMeta({ middleware: 'auth' })
</script>6. Get the current user
To show who's signed in, read the current user from the stored token. On the server you pass the token explicitly; in Nuxt the composable keeps it reactive for you:
// In a Server Component
import { cookies } from 'next/headers'
import { createClient } from '@dyrected/sdk'
const client = createClient({ baseUrl: process.env.NEXT_PUBLIC_DYRECTED_URL! })
async function getUser() {
const token = cookies().get('dyrected-token')?.value
if (!token) return null
client.setToken(token)
try {
return await client.collection('users').me()
} catch {
return null
}
}<script setup lang="ts">
const { user } = useDyrectedAuth('users')
// reactive — updates automatically on login/logout
</script>
<template>
<span v-if="user">{{ user.name }}</span>
</template>Auth endpoint reference
All endpoints are prefixed with /api/collections/{slug}.
| Method | Path | Description |
|---|---|---|
POST | /{slug}/login | Email + password login. Returns { token, user }. |
POST | /{slug}/logout | Stateless — returns { success: true }. Client discards the token. |
GET | /{slug}/me | Returns the current user document (requires Authorization: Bearer <token>). |
POST | /{slug}/refresh-token | Exchange a valid JWT for a fresh one with a new expiry. |
POST | /{slug}/forgot-password | Send a password-reset email. Accepts { email, resetUrl? }. Always returns 200. |
POST | /{slug}/reset-password | Reset password using the token from the reset email. Accepts { token, password }. |
POST | /{slug}/invite | Send an invite email. Requires auth. Accepts { email }. |
POST | /{slug}/accept-invite | Create an account from an invite token. Accepts { token, password, ...extraFields }. Returns { token, user }. |
The password field is always stripped from API read responses.
Next steps
- Invite-only registration — restrict sign-up to invited users
- Role-based access control — admin / editor / viewer roles
- Separating Admin Auth — isolate dashboard access with
__admins