Skip to content

Authentication Guide

Implement authentication in your Nevr application.

Using the Auth Plugin

The fastest way to add auth:

typescript
import { nevr } from "nevr"
import { auth } from "nevr/plugins/auth"

const api = nevr({
  entities: [post],
  driver: prisma(db),
  plugins: [
    auth({
      session: { expiresIn: "7d" },
      emailAndPassword: { enabled: true },
    }),
  ],
})

This provides:

  • User registration
  • Email/password login
  • Session management
  • Protected routes

Protecting Routes

Entity-Level

typescript
const post = entity("post", {
  title: string,
  author: belongsTo(() => user),
})
  .ownedBy("author")
  .rules({
    create: ["authenticated"],
    read: ["everyone"],
    update: ["owner"],
    delete: ["owner", "admin"],
  })

Action-Level

typescript
.actions({
  publish: action()
    .onResource()
    .rules("owner")
    .handler(async (ctx) => {
      // ctx.user is guaranteed to exist
    }),
})

Accessing Current User

In actions and hooks:

typescript
.handler(async (ctx) => {
  const user = ctx.user
  // { id, email, name, role, ... }

  if (!user) {
    throw unauthorizedError("Login required")
  }
})

Frontend Integration

React

tsx
import { useSession } from "@nevr/client/react"

function App() {
  const { user, isLoading } = useSession()

  if (isLoading) return <div>Loading...</div>

  return user ? <Dashboard /> : <LoginForm />
}

Login Form

tsx
function LoginForm() {
  const [email, setEmail] = useState("")
  const [password, setPassword] = useState("")

  const handleSubmit = async (e) => {
    e.preventDefault()
    try {
      await client.auth.signIn.email({ email, password })
    } catch (error) {
      alert(error.message)
    }
  }

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
      />
      <input
        type="password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
      />
      <button type="submit">Sign In</button>
    </form>
  )
}

Custom Auth Logic

Custom Rules

typescript
import { defineRule } from "nevr"

const premiumUser = defineRule("premium", async (ctx) => {
  if (!ctx.user) return false
  const sub = await ctx.driver.findOne("subscription", {
    where: { userId: ctx.user.id, status: "active" },
  })
  return sub?.plan === "premium"
})

const post = entity("post", { ... })
  .rules({
    create: [premiumUser],
  })

JWT Authentication

If using JWT instead of sessions:

typescript
// Custom middleware
const jwtMiddleware = {
  name: "jwt",
  fn: async (ctx, next) => {
    const token = ctx.headers.authorization?.replace("Bearer ", "")
    if (token) {
      try {
        ctx.user = jwt.verify(token, JWT_SECRET)
      } catch {
        // Invalid token
      }
    }
    return next()
  },
}

const api = nevr({
  middleware: [jwtMiddleware],
})

OAuth Providers

typescript
auth({
  socialProviders: {
    google: {
      clientId: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET,
    },
    github: {
      clientId: process.env.GITHUB_CLIENT_ID,
      clientSecret: process.env.GITHUB_CLIENT_SECRET,
    },
  },
})

Role-Based Access

typescript
const user = entity("user", {
  email: string.email().unique(),
  role: string.default("user"), // "user" | "admin" | "moderator"
})

const admin = defineRule("admin", (ctx) => ctx.user?.role === "admin")
const moderator = defineRule("moderator", (ctx) =>
  ["admin", "moderator"].includes(ctx.user?.role)
)

const post = entity("post", { ... })
  .rules({
    delete: [admin],
    update: ["owner", moderator],
  })

Next Steps

Released under the MIT License.