Skip to content

Commit

Permalink
Merge branch 'main' into ndom91/update-licenses
Browse files Browse the repository at this point in the history
  • Loading branch information
ndom91 committed Apr 29, 2024
2 parents aecc099 + 46f9582 commit 1832c01
Show file tree
Hide file tree
Showing 15 changed files with 86 additions and 144 deletions.
1 change: 1 addition & 0 deletions apps/proxy/package.json
Expand Up @@ -3,6 +3,7 @@
"description": "Proxy for Auth.js hosted examples",
"private": true,
"type": "module",
"packageManager": "pnpm@8.9.2",
"dependencies": {
"@auth/core": "latest"
}
Expand Down
5 changes: 0 additions & 5 deletions docs/package.json
Expand Up @@ -11,7 +11,6 @@
"start": "next start",
"lint": "eslint ./{components,pages,utils}",
"lint:fix": "eslint ./{components,pages,utils} --fix",
"postinstall": "pnpm exec simple-git-hooks",
"build:sitemap": "next-sitemap --config next-sitemap.config.cjs"
},
"repository": {
Expand Down Expand Up @@ -47,12 +46,8 @@
"autoprefixer": "^10.4.19",
"postcss": "^8.4.38",
"shiki": "^1.3.0",
"simple-git-hooks": "^2.11.1",
"tailwindcss": "^3.4.3",
"typedoc": "^0.25.13",
"typedoc-plugin-markdown": "4.0.0-next.54"
},
"simple-git-hooks": {
"pre-commit": "pnpm lint"
}
}
2 changes: 1 addition & 1 deletion docs/pages/concepts/session-strategies.mdx
Expand Up @@ -9,7 +9,7 @@ When a user visits your application, you want them to not have to log in every s
evaluate based on the requirements of your application.
</Callout>

You can configure the session strategy using the [`session.strategy`](/reference/core#session) option in the main Auth.js config file.
You can configure the session strategy using the [`session.strategy`](/reference/core#strategy) option in the main Auth.js config file.

## JWT Session

Expand Down
36 changes: 23 additions & 13 deletions docs/pages/getting-started/adapters/mongodb.mdx
Expand Up @@ -79,33 +79,43 @@ The MongoDB adapter does not handle connections automatically, so you will have

```ts filename="lib/db.ts"
// This approach is taken from https://github.com/vercel/next.js/tree/canary/examples/with-mongodb
import { MongoClient } from "mongodb"
import { MongoClient, ServerApiVersion } from "mongodb";

if (!process.env.MONGODB_URI) {
throw new Error('Invalid/Missing environment variable: "MONGODB_URI"')
throw new Error('Invalid/Missing environment variable: "MONGODB_URI"');
}

const uri = process.env.MONGODB_URI
const options = {}
const uri = process.env.MONGODB_URI;
const options = {
serverApi: {
version: ServerApiVersion.v1,
strict: true,
deprecationErrors: true,
},
};

let client
let clientPromise: Promise<MongoClient>
let client;
let clientPromise: Promise<MongoClient>;

if (process.env.NODE_ENV === "development") {
// In development mode, use a global variable so that the value
// is preserved across module reloads caused by HMR (Hot Module Replacement).
if (!global._mongoClientPromise) {
client = new MongoClient(uri, options)
global._mongoClientPromise = client.connect()
let globalWithMongo = global as typeof globalThis & {
_mongoClientPromise?: Promise<MongoClient>;
};

if (!globalWithMongo._mongoClientPromise) {
client = new MongoClient(uri, options);
globalWithMongo._mongoClientPromise = client.connect();
}
clientPromise = global._mongoClientPromise
clientPromise = globalWithMongo._mongoClientPromise;
} else {
// In production mode, it's best to not use a global variable.
client = new MongoClient(uri, options)
clientPromise = client.connect()
client = new MongoClient(uri, options);
clientPromise = client.connect();
}

// Export a module-scoped MongoClient promise. By doing this in a
// separate module, the client can be shared across functions.
export default clientPromise
export default clientPromise;
```
2 changes: 1 addition & 1 deletion docs/pages/getting-started/migrating-to-v5.mdx
Expand Up @@ -142,7 +142,7 @@ Imports from `next-auth/react` are now marked with the [`"use client"`](https://
For advanced use cases, you can use `auth` as a wrapper for your Middleware:

```ts filename="middleware.ts"
import { auth } from "./auth"
import { auth } from "@/auth"

export default auth((req) => {
// req.auth
Expand Down
2 changes: 2 additions & 0 deletions docs/pages/getting-started/providers/google.mdx
Expand Up @@ -108,6 +108,8 @@ export const { handlers, auth, signIn, signOut } = NextAuth({
})
```

For more information on exchanging a code for an access token and refresh token see the [Google OAuth documentation](https://developers.google.com/identity/openid-connect/openid-connect#exchangecode).

### Email Verified

Google also returns a `email_verified` boolean property in the OAuth profile.
Expand Down
73 changes: 4 additions & 69 deletions docs/pages/getting-started/providers/workos.mdx
@@ -1,3 +1,7 @@
---
title: WorkOS
---

import { Callout } from "nextra/components"
import { Code } from "@/components/Code"

Expand Down Expand Up @@ -81,72 +85,3 @@ app.use(
</Code.Express>
</Code>

### Notes

WorkOS is not an identity provider itself, but, rather, a bridge to multiple single sign-on (SSO) providers.
As a result, we need to make some additional changes to authenticate users using WorkOS.

In order to sign a user in using WorkOS, we need to specify which WorkOS Connection to use.
A common way to do this is to collect the user's email address and extract the domain. This can be done using a custom login page.
To add a custom login page, you can use the `pages` option:

```js filename="pages/api/auth/[...nextauth].js"
pages: {
signIn: "/auth/signin",
}
```

We can then add a custom login page that displays an input where the user can enter their email address.
We then extract the domain from the user's email address and pass it to the `authorizationParams` parameter on the `signIn` function:

```js filename="pages/auth/signin.js"
import { useState } from "react"
import { getProviders, signIn } from "next-auth/react"

export default function SignIn({ providers }) {
const [email, setEmail] = useState("")

return (
<>
{Object.values(providers).map((provider) => {
if (provider.id === "workos") {
return (
<div key={provider.id}>
<input
type="email"
value={email}
placeholder="Email"
onChange={(event) => setEmail(event.target.value)}
/>
<button
onClick={() =>
signIn(provider.id, undefined, {
domain: email.split("@")[1],
})
}
>
Sign in with SSO
</button>
</div>
)
}

return (
<div key={provider.id}>
<button onClick={() => signIn(provider.id)}>
Sign in with {provider.name}
</button>
</div>
)
})}
</>
)
}

export async function getServerSideProps(context) {
const providers = await getProviders()
return {
props: { providers },
}
}
```
5 changes: 3 additions & 2 deletions docs/pages/getting-started/session-management/protecting.mdx
Expand Up @@ -221,11 +221,12 @@ export { auth as middleware } from "@/auth"
You can also use the `auth` method as a wrapper if you'd like to implement more logic inside the middleware.

```ts filename="middleware.ts"
import { auth } from "./auth"
import { auth } from "@/auth"

export default auth((req) => {
if (!req.auth) {
return NextResponse.redirect("/login")
const url = req.url.replace(req.nextUrl.pathname, "/login")
return Response.redirect(url)
}
})
```
Expand Down
3 changes: 1 addition & 2 deletions docs/pages/guides/configuring-resend.mdx
Expand Up @@ -40,8 +40,7 @@ export const { handlers, auth } = NextAuth({
```

```ts filename="app/api/auth/[...nextauth]/route.ts"
export { GET, POST } from "./auth"
export const runtime = "edge" // optional
export { GET, POST } from "@/auth"
```

Since this is a [catch-all dynamic route](https://nextjs.org/docs/pages/building-your-application/routing/dynamic-routes#catch-all-segments), it will respond to all the relevant Auth.js API routes so that your application can interact with the chosen OAuth provider using the [OAuth 2](https://oauth.net/2) protocol.
Expand Down
46 changes: 25 additions & 21 deletions docs/pages/guides/refresh-token-rotation.mdx
Expand Up @@ -26,14 +26,13 @@ Depending on the session strategy, `refresh_token` can be persisted either in a

Using the [jwt](/reference/core/types#jwt) and [session](/reference/core/types#session) callbacks, we can persist OAuth tokens and refresh them when they expire.

Below is a sample implementation using Google's Identity Provider. Please note that the OAuth 2.0 request in the `refreshAccessToken()` function will vary between different providers, but the core logic should remain similar.
Below is a sample implementation using Google's Identity Provider. Please note that the OAuth 2.0 request to get the `refresh_token` will vary between different providers, but the core logic should remain similar.

<Code>
<Code.Next>

```ts filename="./auth.ts"
import NextAuth from "next-auth"
import { type TokenSet } from "next-auth/types"
import Google from "next-auth/providers/google"

export const { handlers, auth } = NextAuth({
Expand All @@ -47,32 +46,35 @@ export const { handlers, auth } = NextAuth({
callbacks: {
async jwt({ token, account }) {
if (account) {
// Save the access token and refresh token in the JWT on the initial login
// Save the access token and refresh token in the JWT on the initial login, as well as the user details
return {
access_token: account.access_token,
expires_at: Math.floor(Date.now() / 1000 + account.expires_in),
expires_at: account.expires_at,
refresh_token: account.refresh_token,
user: token
}
} else if (Date.now() < token.expires_at * 1000) {
// If the access token has not expired yet, return it
return token
} else {
if (!token.refresh_token) throw new Error("Missing refresh token")

// If the access token has expired, try to refresh it
try {
// https://accounts.google.com/.well-known/openid-configuration
// We need the `token_endpoint`.
const response = await fetch("https://oauth2.googleapis.com/token", {
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
client_id: process.env.AUTH_GOOGLE_ID,
client_secret: process.env.AUTH_GOOGLE_SECRET,
client_id: process.env.AUTH_GOOGLE_ID!,
client_secret: process.env.AUTH_GOOGLE_SECRET!,
grant_type: "refresh_token",
refresh_token: token.refresh_token,
}),
method: "POST",
})

const tokens: TokenSet = await response.json()
const tokens = await response.json()

if (!response.ok) throw tokens

Expand All @@ -93,7 +95,10 @@ export const { handlers, auth } = NextAuth({
},
async session({ session, token }) {
session.error = token.error
return session
return {
...session,
...token
}
},
},
})
Expand All @@ -119,21 +124,20 @@ declare module "next-auth/jwt" {

### Database strategy

Using the database strategy is very similar, but instead of preserving the `access_token` and `refresh_token`, we save it, well, in the database.
Using the database session strategy is very similar, but instead of preserving the `access_token` and `refresh_token` in the JWT, we will save it in the database by updating the `account` value.

<Code>
<Code.Next>

```ts filename="./auth.ts"
import NextAuth from "next-auth"
import { type TokenSet } from "next-auth/types"
import Google from "next-auth/providers/google"
import { PrismaAdapter } from "@auth/prisma-adapter"
import { PrismaClient } from "@prisma/client"

const prisma = new PrismaClient()

export const { handlers, auth } = NextAuth({
export const { handlers, signIn, signOut, auth } = NextAuth({
adapter: PrismaAdapter(prisma),
providers: [
Google({
Expand All @@ -144,39 +148,39 @@ export const { handlers, auth } = NextAuth({
],
callbacks: {
async session({ session, user }) {
const [google] = await prisma.account.findMany({
const [googleAccount] = await prisma.account.findMany({
where: { userId: user.id, provider: "google" },
})
if (google.expires_at * 1000 < Date.now()) {
if (googleAccount.expires_at * 1000 < Date.now()) {
// If the access token has expired, try to refresh it
try {
// https://accounts.google.com/.well-known/openid-configuration
// We need the `token_endpoint`.
const response = await fetch("https://oauth2.googleapis.com/token", {
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
client_id: process.env.AUTH_GOOGLE_ID,
client_secret: process.env.AUTH_GOOGLE_SECRET,
client_id: process.env.AUTH_GOOGLE_ID!,
client_secret: process.env.AUTH_GOOGLE_SECRET!,
grant_type: "refresh_token",
refresh_token: google.refresh_token,
refresh_token: googleAccount.refresh_token,
}),
method: "POST",
})

const tokens: TokenSet = await response.json()
const tokens = await response.json()

if (!response.ok) throw tokens

await prisma.account.update({
data: {
access_token: tokens.access_token,
expires_at: Math.floor(Date.now() / 1000 + tokens.expires_in),
refresh_token: tokens.refresh_token ?? google.refresh_token,
refresh_token: tokens.refresh_token ?? googleAccount.refresh_token,
},
where: {
provider_providerAccountId: {
provider: "google",
providerAccountId: google.providerAccountId,
providerAccountId: googleAccount.providerAccountId,
},
},
})
Expand Down Expand Up @@ -212,13 +216,13 @@ declare module "next-auth/jwt" {

### Client Side

The `RefreshAccessTokenError` error that is caught in the `refreshAccessToken()` method is passed to the client. This means that you can direct the user to the sign-in flow if we cannot refresh their token. Don't forget, calling `useSession` client-side, for example, requires your component is wrapped with the `<SessionProvider />`.
The `RefreshAccessTokenError` error that is caught in the `session` callback is passed to the client. This means that you can direct the user to the sign-in flow if we cannot refresh their token. Don't forget, calling `useSession` client-side, for example, requires your component is wrapped with the `<SessionProvider />`.

We can handle this functionality as a side effect:

```ts filename="pages/home.js"
import { signIn, useSession } from "next-auth/react";
import { useEffect } from "react";
import { signIn, useSession } from "next-auth/react";

const HomePage() {
const { data: session } = useSession();
Expand Down
2 changes: 1 addition & 1 deletion packages/adapter-drizzle/package.json
Expand Up @@ -54,7 +54,7 @@
"better-sqlite3": "^9.4.0",
"drizzle-kit": "^0.20.17",
"drizzle-orm": "^0.30.8",
"mysql2": "^3.9.3",
"mysql2": "^3.9.7",
"postgres": "^3.4.3",
"tsx": "^4.7.0"
}
Expand Down
2 changes: 1 addition & 1 deletion packages/adapter-kysely/package.json
Expand Up @@ -44,7 +44,7 @@
"@types/pg": "^8.6.5",
"better-sqlite3": "^8.6.0",
"kysely": "^0.24.2",
"mysql2": "^3.9.4",
"mysql2": "^3.9.7",
"pg": "^8.10.0"
}
}

0 comments on commit 1832c01

Please sign in to comment.