mirror of
https://github.com/SrIzan10/next-auth.git
synced 2026-05-01 10:55:20 +00:00
Compare commits
74 Commits
next-auth@
...
next-auth@
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e71118b996 | ||
|
|
afdb3c8d7c | ||
|
|
fd755bc29e | ||
|
|
59daa0e43f | ||
|
|
58d06ed727 | ||
|
|
82159d3e8f | ||
|
|
abb9fed7aa | ||
|
|
5471c0f675 | ||
|
|
b2da0b38d4 | ||
|
|
b3b8d4be46 | ||
|
|
182e118d9b | ||
|
|
7183b06939 | ||
|
|
bd10e87bf4 | ||
|
|
d07abfe517 | ||
|
|
c1110cdc98 | ||
|
|
8ed038d891 | ||
|
|
b25425795b | ||
|
|
ad1650a817 | ||
|
|
a4a487a22a | ||
|
|
b30de36126 | ||
|
|
41e4e515ad | ||
|
|
cde1f82e3c | ||
|
|
c39782007b | ||
|
|
984a089c15 | ||
|
|
26f8b8c1f1 | ||
|
|
afc9b43c53 | ||
|
|
cdbd9ac2e6 | ||
|
|
3d8cc316f1 | ||
|
|
3b8c568f79 | ||
|
|
16668d307d | ||
|
|
6e15bdcb2d | ||
|
|
7a4bf038b1 | ||
|
|
11ad64f617 | ||
|
|
0a278b9297 | ||
|
|
8c8070f30b | ||
|
|
6442d089c1 | ||
|
|
63398d4c3f | ||
|
|
0d54170e83 | ||
|
|
174f0d6aec | ||
|
|
f91b9dc03d | ||
|
|
8763e4aeb9 | ||
|
|
e936c51575 | ||
|
|
8d7ba75bca | ||
|
|
67038b4022 | ||
|
|
5b7ce98a87 | ||
|
|
1c468f057d | ||
|
|
ef22c5b835 | ||
|
|
a912739b24 | ||
|
|
ae318788c3 | ||
|
|
affa459fcc | ||
|
|
b88a31ef1a | ||
|
|
bc82d6555a | ||
|
|
11954567c2 | ||
|
|
6e28ccf84f | ||
|
|
f542b400ba | ||
|
|
d1b76bc302 | ||
|
|
3f396be5d9 | ||
|
|
bf4916dd70 | ||
|
|
5100784d72 | ||
|
|
3853e16268 | ||
|
|
4c0cc9e614 | ||
|
|
d0112aae61 | ||
|
|
e373ff2473 | ||
|
|
6d6d0a8679 | ||
|
|
8152752cc8 | ||
|
|
966381ac9b | ||
|
|
8199c96b76 | ||
|
|
6a06b8e054 | ||
|
|
68bab17914 | ||
|
|
47b4765941 | ||
|
|
6d45ad4840 | ||
|
|
e5e49aca1c | ||
|
|
ea944ebb86 | ||
|
|
ca8af7fcd5 |
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -78,7 +78,7 @@ jobs:
|
||||
yarn release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
NPM_TOKEN_PKG: ${{ secrets.NPM_TOKEN }}
|
||||
NPM_TOKEN_PKG: ${{ secrets.NPM_TOKEN_PKG }}
|
||||
NPM_TOKEN_ORG: ${{ secrets.NPM_TOKEN_ORG }}
|
||||
release-pr:
|
||||
name: Publish PR
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,5 +1,6 @@
|
||||
# Misc
|
||||
.DS_Store
|
||||
.npmrc
|
||||
|
||||
.env
|
||||
.env.local
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
body {
|
||||
font-family: -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans, sans-serif, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
|
||||
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
|
||||
"Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif,
|
||||
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
padding: 0 1rem 1rem 1rem;
|
||||
max-width: 680px;
|
||||
margin: 0 auto;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
body {
|
||||
font-family: -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans,
|
||||
sans-serif, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif,
|
||||
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
|
||||
"Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif,
|
||||
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
padding: 0 1rem 1rem 1rem;
|
||||
max-width: 680px;
|
||||
margin: 0 auto;
|
||||
|
||||
@@ -36,9 +36,9 @@
|
||||
|
||||
<style>
|
||||
:global(body) {
|
||||
font-family: -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans,
|
||||
sans-serif, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif,
|
||||
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
|
||||
"Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif,
|
||||
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
padding: 0 1rem 1rem 1rem;
|
||||
max-width: 680px;
|
||||
margin: 0 auto;
|
||||
|
||||
@@ -1201,9 +1201,9 @@ minimatch@^3.0.4:
|
||||
brace-expansion "^1.1.7"
|
||||
|
||||
minimist@^1.2.0, minimist@^1.2.5:
|
||||
version "1.2.5"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
|
||||
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
|
||||
version "1.2.6"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
|
||||
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
|
||||
|
||||
mkdirp@^0.5.1:
|
||||
version "0.5.5"
|
||||
|
||||
@@ -119,6 +119,8 @@ NextAuthTable:
|
||||
KeyType: RANGE
|
||||
GlobalSecondaryIndexes:
|
||||
- IndexName: GSI1
|
||||
Projection:
|
||||
ProjectionType: ALL
|
||||
KeySchema:
|
||||
- AttributeName: GSI1PK
|
||||
KeyType: HASH
|
||||
|
||||
@@ -133,12 +133,22 @@ npx prisma migrate dev
|
||||
|
||||
### MongoDB
|
||||
|
||||
Prisma supports MongoDB, and so does NextAuth.js. Following the instructions of the [Prisma documentation](https://www.prisma.io/docs/concepts/database-connectors/mongodb) on the MongoDB connector, the only thing you have to change is making sure that the `id` fields are mapped correctly:
|
||||
Prisma supports MongoDB, and so does NextAuth.js. Following the instructions of the [Prisma documentation](https://www.prisma.io/docs/concepts/database-connectors/mongodb) on the MongoDB connector, things you have to change are:
|
||||
|
||||
1. Make sure that the id fields are mapped correctly
|
||||
|
||||
```prisma
|
||||
id String @id @default(auto()) @map("_id") @db.ObjectId
|
||||
```
|
||||
|
||||
2. The Native database type attribute to `@db.String` from `@db.Text`.
|
||||
|
||||
```prisma
|
||||
refresh_token String? @db.String
|
||||
access_token String? @db.String
|
||||
id_token String? @db.String
|
||||
```
|
||||
|
||||
Everything else should be the same.
|
||||
|
||||
## Naming Conventions
|
||||
|
||||
@@ -87,10 +87,11 @@ The default redirect callback looks like this:
|
||||
```js title="pages/api/auth/[...nextauth].js"
|
||||
...
|
||||
callbacks: {
|
||||
redirect({ url, baseUrl }) {
|
||||
if (url.startsWith(baseUrl)) return url
|
||||
async redirect({ url, baseUrl }) {
|
||||
// Allows relative callback URLs
|
||||
else if (url.startsWith("/")) return new URL(url, baseUrl).toString()
|
||||
if (url.startsWith("/")) return new URL(url, baseUrl).toString()
|
||||
// Allows callback URLs on the same origin
|
||||
else if (new URL(url).origin === baseUrl) return url
|
||||
return baseUrl
|
||||
}
|
||||
}
|
||||
@@ -104,7 +105,7 @@ The redirect callback may be invoked more than once in the same flow.
|
||||
## JWT callback
|
||||
|
||||
This callback is called whenever a JSON Web Token is created (i.e. at sign
|
||||
in) or updated (i.e whenever a session is accessed in the client). The returned value will be [signed and optionally encrypted](/configuration/options#jwt), and it is stored in a cookie.
|
||||
in) or updated (i.e whenever a session is accessed in the client). The returned value will be [encrypted](/configuration/options#jwt), and it is stored in a cookie.
|
||||
|
||||
Requests to `/api/auth/signin`, `/api/auth/session` and calls to `getSession()`, `useSession()` will invoke this function, but only if you are using a [JWT session](/configuration/options#session). This method is not invoked when you persist sessions in a database.
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ Sent when an account in a given provider is linked to a user in our user databas
|
||||
The message object will contain:
|
||||
|
||||
- `user`: The user object from your adapter.
|
||||
- `providerAccount`: The object returned from the provider.
|
||||
- `account`: The object returned from the provider.
|
||||
|
||||
### session
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ Default values for this option are shown below:
|
||||
```js
|
||||
session: {
|
||||
// Choose how you want to save the user session.
|
||||
// The default is `"jwt"`, an encrypted JWT (JWE) in the session cookie.
|
||||
// The default is `"jwt"`, an encrypted JWT (JWE) stored in the session cookie.
|
||||
// If you use an `adapter` however, we default it to `"database"` instead.
|
||||
// You can still force a JWT session by explicitly defining `"jwt"`.
|
||||
// When using `"database"`, the session cookie will only contain a `sessionToken` value,
|
||||
@@ -123,7 +123,7 @@ session: {
|
||||
|
||||
#### Description
|
||||
|
||||
JSON Web Tokens can be used for session tokens if enabled with `session: { strategy: "jwt" }` option. JSON Web Tokens are enabled by default if you have not specified an adapter. JSON Web Tokens are encrypted (JWE) by default. We recommend you keep this behaviour. See the [Override JWT `encode` and `decode` methods] advanced option.(#override-jwt-encode-and-decode-methods)
|
||||
JSON Web Tokens can be used for session tokens if enabled with `session: { strategy: "jwt" }` option. JSON Web Tokens are enabled by default if you have not specified an adapter. JSON Web Tokens are encrypted (JWE) by default. We recommend you keep this behaviour. See the [Override JWT `encode` and `decode` methods](#override-jwt-encode-and-decode-methods) advanced option.
|
||||
|
||||
#### JSON Web Token Options
|
||||
|
||||
@@ -495,7 +495,7 @@ jwt: {
|
||||
async decode(params: {
|
||||
token: string
|
||||
secret: string
|
||||
}: Promise<JWT | null>) {
|
||||
}): Promise<JWT | null> {
|
||||
// return a `JWT` object, or `null` if decoding failed
|
||||
return {}
|
||||
},
|
||||
|
||||
@@ -21,8 +21,33 @@ Without going into too much detail, the OAuth flow generally has 6 parts:
|
||||
5. The application requests the resource from the resource server (API) and presents the access token for authentication
|
||||
6. If the access token is valid, the resource server (API) serves the resource to the application
|
||||
|
||||
<img src="https://i2.wp.com/blogs.innovationm.com/wp-content/uploads/2019/07/blog-open1.png" alt="OAuth Flow Diagram" /><br />
|
||||
<small>Source: https://dzone.com/articles/open-id-connect-authentication-with-oauth20-author</small>
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Browser
|
||||
participant App Server
|
||||
participant Auth Server (Github)
|
||||
Note left of Browser: User clicks on "Sign in"
|
||||
Browser->>App Server: GET<br/>"api/auth/signin"
|
||||
App Server->>App Server: Computes the available<br/>sign in providers<br/>from the "providers" option
|
||||
App Server->>Browser: Redirects to Sign in page
|
||||
Note left of Browser: Sign in options<br/>are shown the user<br/>(Github, Twitter, etc...)
|
||||
Note left of Browser: User clicks on<br/>"Sign in with Github"
|
||||
Browser->>App Server: POST<br/>"api/auth/signin/github"
|
||||
App Server->>App Server: Computes sign in<br/>options for Github<br/>(scopes, callback URL, etc...)
|
||||
App Server->>Auth Server (Github): GET<br/>"github.com/login/oauth/authorize"
|
||||
Note left of Auth Server (Github): Sign in options<br> are supplied as<br/>query params<br/>(clientId, <br/>scope, etc...)
|
||||
Auth Server (Github)->>Browser: Shows sign in page<br/>in Github.com<br/>to the user
|
||||
Note left of Browser: User inserts their<br/>credentials in Github
|
||||
Browser->>Auth Server (Github): Github validates the inserted credentials
|
||||
Auth Server (Github)->>Auth Server (Github): Generates one time access code<br/>and calls callback<br>URL defined in<br/>App settings
|
||||
Auth Server (Github)->>App Server: GET<br/>"api/auth/github/callback?code=123"
|
||||
App Server->>App Server: Grabs code<br/>to exchange it for<br/>access token
|
||||
App Server->>Auth Server (Github): POST<br/>"github.com/login/oauth/access_token"<br/>{code: 123}
|
||||
Auth Server (Github)->>Auth Server (Github): Verifies code is<br/>valid and generates<br/>access token
|
||||
Auth Server (Github)->>App Server: { access_token: 16C7x... }
|
||||
App Server->>App Server: Generates session token<br/>and stores session
|
||||
App Server->>Browser: You're now logged in!
|
||||
```
|
||||
|
||||
For more details, check out Aaron Parecki's blog post [OAuth2 Simplified](https://aaronparecki.com/oauth-2-simplified/) or Postman's blog post [OAuth 2.0: Implicit Flow is Dead, Try PKCE Instead](https://blog.postman.com/pkce-oauth-how-to/).
|
||||
|
||||
|
||||
@@ -270,7 +270,7 @@ Ultimately if your request is not accepted or is not actively in development, yo
|
||||
</summary>
|
||||
<p>
|
||||
|
||||
NextAuth.js by default uses JSON Web Tokens for saving the user's session. However, if you use a [database adapter](/adapters/overview), the database will be used to persist the user's session. You can force the usage of JWT when using a database [through the configuration options](/configuration/options#session).
|
||||
NextAuth.js by default uses JSON Web Tokens for saving the user's session. However, if you use a [database adapter](/adapters/overview), the database will be used to persist the user's session. You can force the usage of JWT when using a database [through the configuration options](/configuration/options#session). Since v4 all our JWT tokens are now encrypted by default with A256GCM.
|
||||
|
||||
</p>
|
||||
</details>
|
||||
@@ -285,11 +285,9 @@ JSON Web Tokens can be used for session tokens, but are also used for lots of ot
|
||||
|
||||
- Advantages of using a JWT as a session token include that they do not require a database to store sessions, this can be faster and cheaper to run and easier to scale.
|
||||
|
||||
- JSON Web Tokens in NextAuth.js are secured using cryptographic signing (JWS) by default and it is easy for services and API endpoints to verify tokens without having to contact a database to verify them.
|
||||
- JSON Web Tokens in NextAuth.js are secured using cryptographic encryption (JWE) to store the included information directly in a JWT session token. You may then use the token to pass information between services and APIs on the same domain without having to contact a database to verify the included information.
|
||||
|
||||
- You can enable encryption (JWE) to store include information directly in a JWT session token that you wish to keep secret and use the token to pass information between services / APIs on the same domain.
|
||||
|
||||
- You can use JWT to securely store information you do not mind the client knowing even without encryption, as the JWT is stored in a server-readable-only-token so data in the JWT is not accessible to third party JavaScript running on your site.
|
||||
- You can use JWT to securely store information you do not mind the client knowing even without encryption, as the JWT is stored in a server-readable-only cookie so data in the JWT is not accessible to third party JavaScript running on your site.
|
||||
|
||||
</p>
|
||||
</details>
|
||||
@@ -308,7 +306,7 @@ JSON Web Tokens can be used for session tokens, but are also used for lots of ot
|
||||
|
||||
- As with database session tokens, JSON Web Tokens are limited in the amount of data you can store in them. There is typically a limit of around 4096 bytes per cookie, though the exact limit varies between browsers, proxies and hosting services. If you want to support most browsers, then do not exceed 4096 bytes per cookie. If you want to save more data, you will need to persist your sessions in a database (Source: [browsercookielimits.iain.guru](http://browsercookielimits.iain.guru/))
|
||||
|
||||
The more data you try to store in a token and the more other cookies you set, the closer you will come to this limit. If you wish to store more than ~4 KB of data you're probably at the point where you need to store a unique ID in the token and persist the data elsewhere (e.g. in a server-side key/value store).
|
||||
The more data you try to store in a token and the more other cookies you set, the closer you will come to this limit. Since v4 we have implemented cookie chunking so that cookies over the 4kb limit get split and reassembled upon parsing. However since this data needs to be transmitted on every request, if you wish to store more than ~4 KB of data you're probably at the point where you want to store a unique ID in the token and persist the data elsewhere (e.g. in a server-side key/value store).
|
||||
|
||||
- Data stored in an encrypted JSON Web Token (JWE) may be compromised at some point.
|
||||
|
||||
@@ -316,9 +314,8 @@ JSON Web Tokens can be used for session tokens, but are also used for lots of ot
|
||||
|
||||
Avoid storing any data in a token that might be problematic if it were to be decrypted in the future.
|
||||
|
||||
- If you do not explicitly specify a secret for for NextAuth.js, existing sessions will be invalidated any time your NextAuth.js configuration changes, as NextAuth.js will default to an auto-generated secret.
|
||||
- If you do not explicitly specify a secret for for NextAuth.js, existing sessions will be invalidated any time your NextAuth.js configuration changes, as NextAuth.js will default to an auto-generated secret. Since v4 this only impacts development and generating a secret is required in production.
|
||||
|
||||
If using JSON Web Token you should at least specify a secret and ideally configure public/private keys.
|
||||
|
||||
</p>
|
||||
</details>
|
||||
|
||||
@@ -123,16 +123,14 @@ export default function App({
|
||||
}
|
||||
|
||||
function Auth({ children }) {
|
||||
const { data: session, status } = useSession({ required: true })
|
||||
const isUser = !!session?.user
|
||||
// if `{ required: true }` is supplied, `status` can only be "loading" or "authenticated"
|
||||
const { status } = useSession({ required: true })
|
||||
|
||||
if (isUser) {
|
||||
return children
|
||||
if (status === 'loading') {
|
||||
return <div>Loading...</div>
|
||||
}
|
||||
|
||||
// Session is being fetched, or no user.
|
||||
// If no user, useEffect() will redirect.
|
||||
return <div>Loading...</div>
|
||||
|
||||
return children
|
||||
}
|
||||
```
|
||||
|
||||
@@ -312,11 +310,11 @@ You can specify a different `callbackUrl` by specifying it as the second argumen
|
||||
|
||||
e.g.
|
||||
|
||||
- `signIn(null, { callbackUrl: 'http://localhost:3000/foo' })`
|
||||
- `signIn('google', { callbackUrl: 'http://localhost:3000/foo' })`
|
||||
- `signIn(undefined, { callbackUrl: '/foo' })`
|
||||
- `signIn('google', { callbackUrl: 'http://localhost:3000/bar' })`
|
||||
- `signIn('email', { email, callbackUrl: 'http://localhost:3000/foo' })`
|
||||
|
||||
The URL must be considered valid by the [redirect callback handler](/configuration/callbacks#redirect-callback). By default it requires the URL to be an absolute URL at the same host name, or else it will redirect to the homepage. You can define your own [redirect callback](/configuration/callbacks#redirect-callback) to allow other URLs, including supporting relative URLs.
|
||||
The URL must be considered valid by the [redirect callback handler](/configuration/callbacks#redirect-callback). By default it requires the URL to be an absolute URL at the same host name, or a relative url starting with a slash. If it does not match it will redirect to the homepage. You can define your own [redirect callback](/configuration/callbacks#redirect-callback) to allow other URLs.
|
||||
|
||||
### Using the `redirect: false` option
|
||||
|
||||
@@ -400,7 +398,7 @@ As with the `signIn()` function, you can specify a `callbackUrl` parameter by pa
|
||||
|
||||
e.g. `signOut({ callbackUrl: 'http://localhost:3000/foo' })`
|
||||
|
||||
The URL must be considered valid by the [redirect callback handler](/configuration/callbacks#redirect-callback). By default this means it must be an absolute URL at the same host name (or else it will default to the homepage); you can define your own custom [redirect callback](/configuration/callbacks#redirect-callback) to allow other URLs, including supporting relative URLs.
|
||||
The URL must be considered valid by the [redirect callback handler](/configuration/callbacks#redirect-callback). By default, it requires the URL to be an absolute URL at the same host name, or you can also supply a relative URL starting with a slash. If it does not match it will redirect to the homepage. You can define your own [redirect callback](/configuration/callbacks#redirect-callback) to allow other URLs.
|
||||
|
||||
### Using the `redirect: false` option
|
||||
|
||||
|
||||
@@ -38,8 +38,7 @@ _Note: Email sign-in requires a database to be configured to store single-use ve
|
||||
- Designed to be secure by default and encourage best practices for safeguarding user data
|
||||
- Uses Cross-Site Request Forgery Tokens on POST routes (sign in, sign out)
|
||||
- Default cookie policy aims for the most restrictive policy appropriate for each cookie
|
||||
- When JSON Web Tokens are enabled, they are signed by default (JWS) with HS512
|
||||
- Use JWT encryption (JWE) by setting the option `encryption: true` (defaults to A256GCM)
|
||||
- When JSON Web Tokens are enabled, they are encrypted by default (JWE) with A256GCM
|
||||
- Auto-generates symmetric signing and encryption keys for developer convenience
|
||||
- Features tab/window syncing and keepalive messages to support short-lived sessions
|
||||
- Attempts to implement the latest guidance published by [Open Web Application Security Project](https://owasp.org/)
|
||||
|
||||
@@ -47,7 +47,7 @@ This will work in code editors with a strong TypeScript integration like VSCode
|
||||
|
||||
Let's look at `Session`:
|
||||
|
||||
```ts title="pages/api/[...nextauth].ts"
|
||||
```ts title="pages/api/auth/[...nextauth].ts"
|
||||
import NextAuth from "next-auth"
|
||||
|
||||
export default NextAuth({
|
||||
|
||||
@@ -331,7 +331,7 @@ The way we save data with adapters have slightly changed. With the new Adapter A
|
||||
- `user_id`/`userId` consistently named `userId`.
|
||||
- `compound_id`/`compoundId` is removed from Account.
|
||||
- `access_token`/`accessToken` is removed from Session.
|
||||
- `email_verified`/`emailVerified` on User is consistently named `email_verified`.
|
||||
- `email_verified`/`emailVerified` on User is consistently named `emailVerified`.
|
||||
- `provider_id`/`providerId` renamed to `provider` on Account
|
||||
- `provider_type`/`providerType` renamed to `type` on Account
|
||||
- `provider_account_id`/`providerAccountId` on Account is consistently named `providerAccountId`
|
||||
@@ -419,8 +419,8 @@ They are designed to be run directly against the database itself. So instead of
|
||||
/* ACCOUNT */
|
||||
ALTER TABLE accounts
|
||||
CHANGE "access_token_expires" "expires_at" int
|
||||
CHANGE "user_id" "userId" varchar(191)
|
||||
ADD CONSTRAINT fk_user_id FOREIGN KEY (user_id) REFERENCES users(id)
|
||||
CHANGE "user_id" "userId" varchar(255)
|
||||
ADD CONSTRAINT fk_user_id FOREIGN KEY (userId) REFERENCES users(id)
|
||||
RENAME COLUMN "provider_id" "provider"
|
||||
RENAME COLUMN "provider_account_id" "providerAccountId"
|
||||
DROP COLUMN "provider_type"
|
||||
@@ -429,14 +429,14 @@ DROP COLUMN "compound_id"
|
||||
DROP COLUMN "created_at"
|
||||
DROP COLUMN "updated_at"
|
||||
|
||||
ADD COLUMN "token_type" varchar(191) NULL
|
||||
ADD COLUMN "scope" varchar(191) NULL
|
||||
ADD COLUMN "id_token" varchar(191) NULL
|
||||
ADD COLUMN "session_state" varchar(191) NULL
|
||||
ADD COLUMN "token_type" varchar(255) NULL
|
||||
ADD COLUMN "scope" varchar(255) NULL
|
||||
ADD COLUMN "id_token" varchar(255) NULL
|
||||
ADD COLUMN "session_state" varchar(255) NULL
|
||||
|
||||
/* Note: These are only needed if you're going to be using the old Twitter OAuth 1.0 provider. */
|
||||
ADD COLUMN "oauth_token_secret" varchar(191) NULL
|
||||
ADD COLUMN "oauth_token" varchar(191) NULL
|
||||
ADD COLUMN "oauth_token_secret" varchar(255) NULL
|
||||
ADD COLUMN "oauth_token" varchar(255) NULL
|
||||
|
||||
/* USER */
|
||||
ALTER TABLE users
|
||||
@@ -448,15 +448,16 @@ DROP COLUMN "updated_at"
|
||||
/* SESSION */
|
||||
ALTER TABLE sessions
|
||||
RENAME COLUMN "session_token" "sessionToken"
|
||||
CHANGE "user_id" "userId" varchar(191)
|
||||
ADD CONSTRAINT fk_user_id FOREIGN KEY (user_id) REFERENCES users(id)
|
||||
CHANGE "user_id" "userId" varchar(255)
|
||||
ADD CONSTRAINT fk_user_id FOREIGN KEY (userId) REFERENCES users(id)
|
||||
DROP COLUMN "access_token"
|
||||
/* The following two timestamp columns have never been necessary for NextAuth.js to function, but can be kept if you want */
|
||||
DROP COLUMN "created_at"
|
||||
DROP COLUMN "updated_at"
|
||||
|
||||
/* VERIFICATION REQUESTS */
|
||||
ALTER TABLE verification_requests
|
||||
ALTER TABLE verification_requests RENAME verification_tokens
|
||||
ALTER TABLE verification_tokens
|
||||
DROP COLUMN id
|
||||
/* The following two timestamp columns have never been necessary for NextAuth.js to function, but can be kept if you want */
|
||||
DROP COLUMN "created_at"
|
||||
@@ -467,50 +468,84 @@ DROP COLUMN "updated_at"
|
||||
|
||||
```sql
|
||||
/* ACCOUNT */
|
||||
ALTER TABLE accounts RENAME COLUMN "user_id" TO "userId";
|
||||
ALTER TABLE accounts RENAME COLUMN "provider_id" TO "provider";
|
||||
ALTER TABLE accounts RENAME COLUMN "provider_account_id" TO "providerAccountId";
|
||||
ALTER TABLE accounts RENAME COLUMN "access_token_expires" TO "expires_at";
|
||||
ALTER TABLE accounts RENAME COLUMN "provider_type" TO "type";
|
||||
|
||||
/* Do conversion of TIMESTAMPTZ to BIGINT */
|
||||
ALTER TABLE accounts ALTER COLUMN "expires_at" TYPE TEXT USING CAST(extract(epoch FROM "expires_at") AS BIGINT)*1000;
|
||||
|
||||
/* Keep id as SERIAL with autoincrement when using ORM. Using new v4 uuid format won't work because of incompatibility */
|
||||
/* ALTER TABLE accounts ALTER COLUMN "id" TYPE TEXT; */
|
||||
/* ALTER TABLE accounts ALTER COLUMN "userId" TYPE TEXT; */
|
||||
ALTER TABLE accounts ALTER COLUMN "type" TYPE TEXT;
|
||||
ALTER TABLE accounts ALTER COLUMN "provider" TYPE TEXT;
|
||||
ALTER TABLE accounts ALTER COLUMN "providerAccountId" TYPE TEXT;
|
||||
|
||||
ALTER TABLE accounts ADD CONSTRAINT fk_user_id FOREIGN KEY ("userId") REFERENCES users(id);
|
||||
ALTER TABLE accounts
|
||||
CHANGE "access_token_expires" "expires_at" int
|
||||
CHANGE "user_id" "userId" text
|
||||
ADD CONSTRAINT fk_user_id FOREIGN KEY (user_id) REFERENCES users(id)
|
||||
RENAME COLUMN "provider_id" "provider"
|
||||
RENAME COLUMN "provider_account_id" "providerAccountId"
|
||||
DROP COLUMN "provider_type"
|
||||
DROP COLUMN "compound_id"
|
||||
DROP COLUMN IF EXISTS "compound_id";
|
||||
/* The following two timestamp columns have never been necessary for NextAuth.js to function, but can be kept if you want */
|
||||
DROP COLUMN "created_at"
|
||||
DROP COLUMN "updated_at"
|
||||
|
||||
ADD COLUMN "token_type" text NULL
|
||||
ADD COLUMN "scope" text NULL
|
||||
ADD COLUMN "id_token" text NULL
|
||||
ADD COLUMN "session_state" text NULL
|
||||
ALTER TABLE accounts
|
||||
DROP COLUMN IF EXISTS "created_at",
|
||||
DROP COLUMN IF EXISTS "updated_at";
|
||||
|
||||
ALTER TABLE accounts
|
||||
ADD COLUMN IF NOT EXISTS "token_type" TEXT NULL,
|
||||
ADD COLUMN IF NOT EXISTS "scope" TEXT NULL,
|
||||
ADD COLUMN IF NOT EXISTS "id_token" TEXT NULL,
|
||||
ADD COLUMN IF NOT EXISTS "session_state" TEXT NULL;
|
||||
/* Note: These are only needed if you're going to be using the old Twitter OAuth 1.0 provider. */
|
||||
ADD COLUMN "oauth_token_secret" text NULL
|
||||
ADD COLUMN "oauth_token" text NULL
|
||||
/* ALTER TABLE accounts
|
||||
ADD COLUMN IF NOT EXISTS "oauth_token_secret" TEXT NULL,
|
||||
ADD COLUMN IF NOT EXISTS "oauth_token" TEXT NULL; */
|
||||
|
||||
/* USER */
|
||||
ALTER TABLE users
|
||||
RENAME COLUMN "email_verified" "emailVerified"
|
||||
ALTER TABLE users RENAME COLUMN "email_verified" TO "emailVerified";
|
||||
|
||||
/* Keep id as SERIAL with autoincrement when using ORM. Using new v4 uuid format won't work because of incompatibility */
|
||||
/* ALTER TABLE users ALTER COLUMN "id" TYPE TEXT; */
|
||||
ALTER TABLE users ALTER COLUMN "name" TYPE TEXT;
|
||||
ALTER TABLE users ALTER COLUMN "email" TYPE TEXT;
|
||||
ALTER TABLE users ALTER COLUMN "image" TYPE TEXT;
|
||||
/* Do conversion of TIMESTAMPTZ to BIGINT and then TEXT */
|
||||
ALTER TABLE users ALTER COLUMN "emailVerified" TYPE TEXT USING CAST(CAST(extract(epoch FROM "emailVerified") AS BIGINT)*1000 AS TEXT);
|
||||
/* The following two timestamp columns have never been necessary for NextAuth.js to function, but can be kept if you want */
|
||||
DROP COLUMN "created_at"
|
||||
DROP COLUMN "updated_at"
|
||||
ALTER TABLE users
|
||||
DROP COLUMN IF EXISTS "created_at",
|
||||
DROP COLUMN IF EXISTS "updated_at";
|
||||
|
||||
/* SESSION */
|
||||
ALTER TABLE sessions
|
||||
RENAME COLUMN "session_token" "sessionToken"
|
||||
CHANGE "user_id" "userId" text
|
||||
ADD CONSTRAINT fk_user_id FOREIGN KEY (user_id) REFERENCES users(id)
|
||||
DROP COLUMN "access_token"
|
||||
ALTER TABLE sessions RENAME COLUMN "session_token" TO "sessionToken";
|
||||
ALTER TABLE sessions RENAME COLUMN "user_id" TO "userId";
|
||||
|
||||
/* Keep id as SERIAL with autoincrement when using ORM. Using new v4 uuid format won't work because of incompatibility */
|
||||
/* ALTER TABLE sessions ALTER COLUMN "id" TYPE TEXT; */
|
||||
/* ALTER TABLE sessions ALTER COLUMN "userId" TYPE TEXT; */
|
||||
ALTER TABLE sessions ALTER COLUMN "sessionToken" TYPE TEXT;
|
||||
ALTER TABLE sessions ADD CONSTRAINT fk_user_id FOREIGN KEY ("userId") REFERENCES users(id);
|
||||
/* Do conversion of TIMESTAMPTZ to BIGINT and then TEXT */
|
||||
ALTER TABLE sessions ALTER COLUMN "expires" TYPE TEXT USING CAST(CAST(extract(epoch FROM "expires") AS BIGINT)*1000 AS TEXT);
|
||||
ALTER TABLE sessions DROP COLUMN IF EXISTS "access_token";
|
||||
/* The following two timestamp columns have never been necessary for NextAuth.js to function, but can be kept if you want */
|
||||
DROP COLUMN "created_at"
|
||||
DROP COLUMN "updated_at"
|
||||
ALTER TABLE sessions
|
||||
DROP COLUMN IF EXISTS "created_at",
|
||||
DROP COLUMN IF EXISTS "updated_at";
|
||||
|
||||
/* VERIFICATION REQUESTS */
|
||||
ALTER TABLE verification_requests
|
||||
DROP COLUMN id
|
||||
ALTER TABLE verification_requests RENAME TO verification_tokens;
|
||||
/* Keep id as ORM needs it */
|
||||
/* ALTER TABLE verification_tokens DROP COLUMN IF EXISTS id; */
|
||||
ALTER TABLE verification_tokens ALTER COLUMN "identifier" TYPE TEXT;
|
||||
ALTER TABLE verification_tokens ALTER COLUMN "token" TYPE TEXT;
|
||||
/* Do conversion of TIMESTAMPTZ to BIGINT and then TEXT */
|
||||
ALTER TABLE verification_tokens ALTER COLUMN "expires" TYPE TEXT USING CAST(CAST(extract(epoch FROM "expires") AS BIGINT)*1000 AS TEXT);
|
||||
/* The following two timestamp columns have never been necessary for NextAuth.js to function, but can be kept if you want */
|
||||
DROP COLUMN "created_at"
|
||||
DROP COLUMN "updated_at"
|
||||
ALTER TABLE verification_tokens
|
||||
DROP COLUMN IF EXISTS "created_at",
|
||||
DROP COLUMN IF EXISTS "updated_at";
|
||||
```
|
||||
|
||||
#### MongoDB
|
||||
|
||||
12
docs/docs/guides/basics.md
Normal file
12
docs/docs/guides/basics.md
Normal file
@@ -0,0 +1,12 @@
|
||||
---
|
||||
id: basics
|
||||
title: Basics
|
||||
---
|
||||
|
||||
### [Securing pages and API routes](/tutorials/securing-pages-and-api-routes)
|
||||
|
||||
- How to restrict access to pages and API routes.
|
||||
|
||||
### [Usage with class components](/tutorials/usage-with-class-components)
|
||||
|
||||
- How to use `useSession()` hook with class components.
|
||||
30
docs/docs/guides/fullstack.md
Normal file
30
docs/docs/guides/fullstack.md
Normal file
@@ -0,0 +1,30 @@
|
||||
---
|
||||
id: fullstack
|
||||
title: Fullstack
|
||||
---
|
||||
|
||||
### [Refresh Token Rotation](/tutorials/refresh-token-rotation)
|
||||
|
||||
- How to implement refresh token rotation.
|
||||
|
||||
### [LDAP Authentication](/tutorials/ldap-auth-example)
|
||||
|
||||
- How to use the Credentials Provider to authenticate against an LDAP database. This approach can be used to authenticate existing user accounts against any backend.
|
||||
|
||||
### [Adding HTTP(S) Proxy Support](/tutorials/corporate-proxy)
|
||||
|
||||
- Add support for HTTP/HTTPS Proxy support to `openid-client` in order to use NextAuth.js behind a corporate proxy or other locked down network.
|
||||
|
||||
### [Using the Email Provider behind Corporate Email Scanning Services](/tutorials/avoid-corporate-link-checking-email-provider)
|
||||
|
||||
- An internal tutorial on modifying the catch-all API Route to gracefully handle `HEAD` requests.
|
||||
|
||||
## Database
|
||||
|
||||
### [Custom models with TypeORM](/adapters/typeorm#custom-models)
|
||||
|
||||
- How to use models with custom properties using the TypeORM adapter.
|
||||
|
||||
### [Creating a database adapter](/tutorials/creating-a-database-adapter)
|
||||
|
||||
- How to create a custom adapter, to use any database to fetch and store user / account data.
|
||||
8
docs/docs/guides/testing.md
Normal file
8
docs/docs/guides/testing.md
Normal file
@@ -0,0 +1,8 @@
|
||||
---
|
||||
id: testing
|
||||
title: Testing
|
||||
---
|
||||
|
||||
### [Testing with Cypress](/tutorials/testing-with-cypress)
|
||||
|
||||
- How to write tests using Cypress.
|
||||
@@ -30,7 +30,7 @@ import BoxyHQSAMLProvider from "next-auth/providers/boxyhq-saml"
|
||||
...
|
||||
providers: [
|
||||
BoxyHQSAMLProvider({
|
||||
issuer: "http://localhost:5000",
|
||||
issuer: "http://localhost:5225",
|
||||
clientId: "dummy", // The dummy here is necessary since we'll pass tenant and product custom attributes in the client code
|
||||
clientSecret: "dummy", // The dummy here is necessary since we'll pass tenant and product custom attributes in the client code
|
||||
})
|
||||
|
||||
@@ -11,6 +11,11 @@ https://developers.google.com/identity/protocols/oauth2
|
||||
|
||||
https://console.developers.google.com/apis/credentials
|
||||
|
||||
The "Authorized redirect URIs" used when creating the credentials must include your full domain and end in the callback path. For example;
|
||||
|
||||
- For production: `https://{YOUR_DOMAIN}/api/auth/callback/google`
|
||||
- For development: `http://localhost:3000/api/auth/callback/google`
|
||||
|
||||
## Options
|
||||
|
||||
The **Google Provider** comes with a set of default options:
|
||||
|
||||
@@ -37,5 +37,5 @@ providers: [
|
||||
```
|
||||
|
||||
:::note
|
||||
`issuer` should include the realm – e.g. `https://my-keycloak-domain.com/auth/realms/My_Realm`
|
||||
`issuer` should include the realm – e.g. `https://my-keycloak-domain.com/realms/My_Realm`
|
||||
:::
|
||||
|
||||
@@ -21,14 +21,6 @@ title: Tutorials and Explainers
|
||||
|
||||
- This tutorial walks one through adding NextAuth.js to an existing project. Including setting up the OAuth client id and secret, adding the API routes for authentication, protecting pages and API routes behind that authentication, etc.
|
||||
|
||||
#### [Securing pages and API routes](tutorials/securing-pages-and-api-routes)
|
||||
|
||||
- How to restrict access to pages and API routes.
|
||||
|
||||
#### [Usage with class components](tutorials/usage-with-class-components)
|
||||
|
||||
- How to use `useSession()` hook with class components.
|
||||
|
||||
#### [Adding social authentication support to a Next.js app](https://getstarted.sh/bulletproof-next/add-social-authentication) <svg xmlns="http://www.w3.org/2000/svg" style={{ marginLeft: '5px', marginBottom:'-6px'}} height="20" width="20" fill="none" viewBox="0 0 24 24" stroke="currentColor"><title>External</title> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /> </svg>
|
||||
|
||||
- A tutorial by Arunoda Susirpiala. Checkout [GetStarted](https://getstarted.sh/) for more examples.
|
||||
@@ -49,8 +41,25 @@ title: Tutorials and Explainers
|
||||
|
||||
- A video tutorial by Xiaoru Li from Prisma.
|
||||
|
||||
#### [How to authenticate Next.js Apps with Sign-In With Ethereum (SIWE) & NextAuth.js](https://docs.login.xyz/integrations/nextauth.js) <svg xmlns="http://www.w3.org/2000/svg" style={{ marginLeft: '5px', marginBottom:'-6px'}} height="20" width="20" fill="none" viewBox="0 0 24 24" stroke="currentColor"><title>External</title> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /> </svg>
|
||||
|
||||
- Learn how to use Sign-In With Ethereum to authenticate your users with their existing Ethereum wallets - identifiers they personally control.
|
||||
|
||||
## Fullstack
|
||||
|
||||
#### [Build a FullStack App with Next.js, NextAuth.js, Supabase & Prisma](https://themodern.dev/courses/build-a-fullstack-app-with-nextjs-supabase-and-prisma-322389284337222224) <svg xmlns="http://www.w3.org/2000/svg" style={{ marginLeft: '5px', marginBottom:'-6px'}} height="20" width="20" fill="none" viewBox="0 0 24 24" stroke="currentColor"><title>External</title> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /> </svg>
|
||||
|
||||
In this [free course](https://themodern.dev/courses/build-a-fullstack-app-with-nextjs-supabase-and-prisma-322389284337222224), you'll learn how to build a full-stack app using the following technologies:
|
||||
|
||||
- **Next.js** - The React framework for building the UI of the app and the REST API
|
||||
- **NextAuth.js** - For implementing passwordless and OAuth authentication
|
||||
- **Supabase** - For persisting the app data into a PostgreSQL database and storing media files
|
||||
- **Prisma** - For making it easy to read and write data from our app from and to the database
|
||||
|
||||
The app that we'll work on in this course is called ***SupaVacation***. It is an online marketplace for vacation rentals where users can browse through all the properties for rent, bookmark their favorite ones, and even rent their own properties.
|
||||
|
||||
> Here's [a live demo](https://supa-vacation.vercel.app/) of the app's final version. It is what your app should look likes after completing this course. Feel free to play with it to get an overview of all the features you'll be working on.
|
||||
|
||||
#### [Magic Link Authentication in Next.js with NextAuth and Fauna](https://alterclass.io/tutorials/magic-link-authentication-in-nextjs-with-nextauth-and-fauna) <svg xmlns="http://www.w3.org/2000/svg" style={{ marginLeft: '5px', marginBottom:'-6px'}} height="20" width="20" fill="none" viewBox="0 0 24 24" stroke="currentColor"><title>External</title> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /> </svg>
|
||||
|
||||
Learn how to implement passwordless/magic link authentication with database storage in your Next.js projects using NextAuth and Fauna DB.
|
||||
@@ -75,22 +84,8 @@ This tutorial covers:
|
||||
|
||||
- This example shows how to implement a full-stack app in TypeScript with Next.js using Prisma Client as a backend. It also demonstrates how to implement authentication using NextAuth.js. By Nikolas Burk at Prisma.
|
||||
|
||||
## Testing
|
||||
|
||||
#### [Testing with Cypress](tutorials/testing-with-cypress)
|
||||
|
||||
- How to write tests using Cypress.
|
||||
|
||||
## Advanced
|
||||
|
||||
#### [Refresh Token Rotation](tutorials/refresh-token-rotation)
|
||||
|
||||
- How to implement refresh token rotation.
|
||||
|
||||
#### [LDAP Authentication](tutorials/ldap-auth-example)
|
||||
|
||||
- How to use the Credentials Provider to authenticate against an LDAP database. This approach can be used to authenticate existing user accounts against any backend.
|
||||
|
||||
#### [Add auth support to a Next.js app with a custom backend](https://arunoda.me/blog/add-auth-support-to-a-next-js-app-with-a-custom-backend) <svg xmlns="http://www.w3.org/2000/svg" style={{ marginLeft: '5px', marginBottom:'-6px'}} height="20" width="20" fill="none" viewBox="0 0 24 24" stroke="currentColor"><title>External</title> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /> </svg>
|
||||
|
||||
- A tutorial by Arunoda Susirpiala.
|
||||
@@ -109,14 +104,6 @@ This tutorial covers:
|
||||
|
||||
## Database
|
||||
|
||||
#### [Custom models with TypeORM](adapters/typeorm#custom-models)
|
||||
|
||||
- How to use models with custom properties using the TypeORM adapter.
|
||||
|
||||
#### [Creating a database adapter](tutorials/creating-a-database-adapter)
|
||||
|
||||
- How to create a custom adapter, to use any database to fetch and store user / account data.
|
||||
|
||||
#### [Using NextAuth.js with Prisma and PlanetScale serverless databases](https://github.com/planetscale/nextjs-planetscale-starter)
|
||||
#### [Using NextAuth.js with Prisma and PlanetScale serverless databases](https://github.com/planetscale/nextjs-planetscale-starter) <svg xmlns="http://www.w3.org/2000/svg" style={{ marginLeft: '5px', marginBottom:'-6px'}} height="20" width="20" fill="none" viewBox="0 0 24 24" stroke="currentColor"><title>External</title> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /> </svg>
|
||||
|
||||
- How to set up a PlanetScale database to fetch and store user / account data with the Prisma adapter.
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
---
|
||||
id: avoid-corporate-link-checking-email-provider
|
||||
title: Allow Email Signups Behind Corporate Link Checker
|
||||
---
|
||||
|
||||
If you use Office 365 or Outlook, or potentially other Email systems, you may notice your Email invitation Links not working.
|
||||
|
||||
This is because the invitation Email your User is receiving is being scanned by the Email provider. In the specific case of Outlook and their "SafeLink" feature, they send a HEAD request to each link in the Email. This request will trigger the NextAuth.js catch-all API Route with the users invitation token, in effect using it up.
|
||||
|
||||
Therefore, when the user wants to use it themselves, and clicks on the invitation link they will be greeted with an error message that the invitation is invalid.
|
||||
|
||||
## Workarounds
|
||||
|
||||
### Disable "SafeLink"
|
||||
|
||||
The first potential workaround is to simply disable this "SafeLink" feature for your organisation. Microsoft has more details on this [here](https://docs.microsoft.com/en-us/microsoft-365/security/office-365-security/safe-links?view=o365-worldwide#do-not-rewrite-the-following-urls-lists-in-safe-links-policies). Obviously this won't be an option for everyone as this is usually a part of corporate IT policy.
|
||||
|
||||
### Update NextAuth.js for 'HEAD' requests
|
||||
|
||||
The second option is to modify your `[...nextauth].js` catch-all API route a bit to gracefully handle these initial `HEAD` requests from the email service, without accidentally using up the invitation link.
|
||||
|
||||
This can be done by simply returning a `200` response on `HEAD` requests at the very top of the API route, before any other logic is executed.
|
||||
|
||||
For example
|
||||
|
||||
```jsx title="/pages/api/auth/[...nextauth].js"
|
||||
import type { NextApiRequest, NextApiResponse } from "next"
|
||||
import NextAuth from "next-auth"
|
||||
|
||||
export default async function auth(req: NextApiRequest, res: NextApiResponse) {
|
||||
|
||||
if(req.method === "HEAD") {
|
||||
return res.status(200)
|
||||
}
|
||||
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
This should allow you to successfully use NextAuth.js's Email provider behind strict corporate IT settings.
|
||||
83
docs/docs/tutorials/corporate-proxy.md
Normal file
83
docs/docs/tutorials/corporate-proxy.md
Normal file
@@ -0,0 +1,83 @@
|
||||
--
|
||||
id: corporate-proxy
|
||||
title: Add support for HTTP Proxy
|
||||
--
|
||||
|
||||
Using NextAuth.js behind a corporate proxy is not supported out of the box. This is due to the fact that the underlying library we use, [`openid-client`](https://npm.im/openid-client), uses the built-in Node.js `http` / `https` libraries, which do not support proxys by default. (See: [`http` docs](https://nodejs.org/dist/latest-v16.x/docs/api/http.html), [`https` docs](https://nodejs.org/dist/latest-v16.x/docs/api/https.html)).
|
||||
|
||||
Therefore, we'll need to an additional proxy agent to the http client, such as `https-proxy-agent`. `openid-client` allows the user to set an `agent` for requests ([Source](https://github.com/panva/node-openid-client/blob/main/docs/README.md#customizing-individual-http-requests).
|
||||
|
||||
Thanks to [raphaelpc](https://github.com/raphaelpc) for the below diff, which when applied to `v4.2.1`, adds this agent support to the `client.js` file.
|
||||
|
||||
```diff
|
||||
diff --git a/node_modules/next-auth/core/lib/oauth/client.js b/node_modules/next-auth/core/lib/oauth/client.js
|
||||
index 77161bd..1082fba 100644
|
||||
--- a/node_modules/next-auth/core/lib/oauth/client.js
|
||||
+++ b/node_modules/next-auth/core/lib/oauth/client.js
|
||||
@@ -7,11 +7,19 @@ exports.openidClient = openidClient;
|
||||
|
||||
var _openidClient = require("openid-client");
|
||||
|
||||
+var HttpsProxyAgent = require("https-proxy-agent");
|
||||
+
|
||||
async function openidClient(options) {
|
||||
const provider = options.provider;
|
||||
- if (provider.httpOptions) _openidClient.custom.setHttpOptionsDefaults(provider.httpOptions);
|
||||
- let issuer;
|
||||
+ let httpOptions = {};
|
||||
+ if (provider.httpOptions) httpOptions = { ...provider.httpOptions };
|
||||
+ if (process.env.http_proxy) {
|
||||
+ let agent = new HttpsProxyAgent(process.env.http_proxy);
|
||||
+ httpOptions.agent = agent;
|
||||
+ }
|
||||
+ _openidClient.custom.setHttpOptionsDefaults(httpOptions);
|
||||
|
||||
+ let issuer;
|
||||
if (provider.wellKnown) {
|
||||
issuer = await _openidClient.Issuer.discover(provider.wellKnown);
|
||||
} else {
|
||||
```
|
||||
|
||||
> For more details, see [this issue](https://github.com/nextauthjs/next-auth/issues/2509#issuecomment-1035410802)
|
||||
|
||||
After applying this patch, we can add the the proxy connecting string via the `http_proxy` environment variable.
|
||||
|
||||
### Provider
|
||||
|
||||
If you're having trouble with your provider when using the `https-proxy-agent`, you may be using a provider which requires an extra request to, for example, fetch the users profile picture. In cases like these, you'll have to add the proxy workaround to your provider config as well. Below is an example of how to do that with the `AzureAD` provider.
|
||||
|
||||
```diff
|
||||
diff --git a/node_modules/next-auth/providers/azure-ad.js b/node_modules/next-auth/providers/azure-ad.js
|
||||
index 73d96d3..536cd81 100644
|
||||
--- a/node_modules/next-auth/providers/azure-ad.js
|
||||
+++ b/node_modules/next-auth/providers/azure-ad.js
|
||||
@@ -5,6 +5,8 @@ Object.defineProperty(exports, "__esModule", {
|
||||
});
|
||||
exports.default = AzureAD;
|
||||
|
||||
+const HttpsProxyAgent = require('https-proxy-agent');
|
||||
+
|
||||
function AzureAD(options) {
|
||||
var _options$tenantId, _options$profilePhoto;
|
||||
|
||||
@@ -22,11 +24,15 @@ function AzureAD(options) {
|
||||
},
|
||||
|
||||
async profile(profile, tokens) {
|
||||
- const profilePicture = await fetch(`https://graph.microsoft.com/v1.0/me/photos/${profilePhotoSize}x${profilePhotoSize}/$value`, {
|
||||
+ let fetchOptions = {
|
||||
headers: {
|
||||
- Authorization: `Bearer ${tokens.access_token}`
|
||||
- }
|
||||
- });
|
||||
+ Authorization: `Bearer ${tokens.access_token}`,
|
||||
+ },
|
||||
+ };
|
||||
+ if (process.env.http_proxy) {
|
||||
+ fetchOptions.agent = new HttpsProxyAgent(process.env.http_proxy);
|
||||
+ }
|
||||
+ const profilePicture = await fetch(`https://graph.microsoft.com/v1.0/me/photos/${profilePhotoSize}x${profilePhotoSize}/$value`, fetchOptions);
|
||||
|
||||
if (profilePicture.ok) {
|
||||
const pictureBuffer = await profilePicture.arrayBuffer();
|
||||
```
|
||||
@@ -7,6 +7,8 @@ Using a custom adapter you can connect to any database back-end or even several
|
||||
|
||||
## How to create an adapter
|
||||
|
||||
For more information about the data these methods need to manage see [models](/adapters/models).
|
||||
|
||||
_See the code below for practical example._
|
||||
|
||||
### Example code
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
"lint:fix": "prettier --write .",
|
||||
"generate-providers": "node ./scripts/generate-providers.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"dependencies": {
|
||||
"@codesandbox/sandpack-react": "^0.13.12",
|
||||
"@docusaurus/core": "^2.0.0-beta.17",
|
||||
"@docusaurus/preset-classic": "^2.0.0-beta.17",
|
||||
|
||||
@@ -74,5 +74,20 @@ module.exports = {
|
||||
"warnings",
|
||||
"errors",
|
||||
"deployment",
|
||||
{
|
||||
type: "category",
|
||||
label: "Guides",
|
||||
collapsed: true,
|
||||
items: [
|
||||
"guides/basics",
|
||||
"guides/fullstack",
|
||||
"guides/testing",
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "html",
|
||||
value: '<script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?serve=CEAI6K3N&placement=next-authjsorg" id="_carbonads_js"></script>',
|
||||
defaultStyle: true
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
@@ -26,9 +26,10 @@
|
||||
--ifm-color-info: #1eb1fc;
|
||||
--ifm-color-success: #1eb1fc;
|
||||
--ifm-color-warning: #c94b4b;
|
||||
--ifm-font-family-base: -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell,
|
||||
Noto Sans, sans-serif, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial,
|
||||
sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
--ifm-font-family-base: ui-sans-serif, system-ui, -apple-system,
|
||||
BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans",
|
||||
sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol",
|
||||
"Noto Color Emoji";
|
||||
--ifm-background-color: #fff;
|
||||
--ifm-footer-background-color: #f9f9f9;
|
||||
--ifm-hero-background-color: #f5f5f5;
|
||||
@@ -194,3 +195,92 @@ html[data-theme="dark"] hr {
|
||||
.inline {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* CarbonAds */
|
||||
|
||||
#carbonads * {
|
||||
margin: initial;
|
||||
padding: initial;
|
||||
}
|
||||
|
||||
#carbonads {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
||||
Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", Helvetica, Arial,
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
#carbonads {
|
||||
display: flex;
|
||||
max-width: 330px;
|
||||
background-color: hsl(0, 0%, 98%);
|
||||
box-shadow: 0 1px 4px 1px hsla(0, 0%, 0%, 0.1);
|
||||
z-index: 100;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
#carbonads a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#carbonads a:hover {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
#carbonads span {
|
||||
position: relative;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#carbonads .carbon-wrap {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#carbonads .carbon-img {
|
||||
display: block;
|
||||
margin: 0;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
#carbonads .carbon-img img {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#carbonads .carbon-text {
|
||||
font-size: 11px;
|
||||
padding: 8px;
|
||||
margin-bottom: 16px;
|
||||
line-height: 1.5;
|
||||
text-align: left;
|
||||
}
|
||||
#carbonads .carbon-poweredby {
|
||||
display: block;
|
||||
padding: 6px 8px;
|
||||
background: #f1f1f2;
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
font-weight: 600;
|
||||
font-size: 8px;
|
||||
line-height: 1;
|
||||
border-top-left-radius: 3px;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
html[data-theme="dark"] #carbonads .carbon-text {
|
||||
color: #ddd;
|
||||
}
|
||||
|
||||
html[data-theme="dark"] #carbonads > span {
|
||||
background: #1a1a1a;
|
||||
box-shadow: 0 0 1px hsl(0deg 0% 0% / 9%), 0 0 2px hsl(0deg 0% 0% / 9%),
|
||||
0 0 4px hsl(0deg 0% 0% / 9%), 0 0 8px hsl(0deg 0% 0% / 9%);
|
||||
}
|
||||
|
||||
html[data-theme="dark"] #carbonads .carbon-poweredby {
|
||||
color: #aaa;
|
||||
background: #1e2021;
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
"semver": "7.3.5",
|
||||
"stream-to-array": "2.3.0",
|
||||
"ts-node": "10.5.0",
|
||||
"turbo": "^1.1.4",
|
||||
"turbo": "^1.1.6",
|
||||
"typescript": "^4.5.2"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@next-auth/dgraph-adapter",
|
||||
"version": "1.0.2",
|
||||
"version": "1.0.3",
|
||||
"description": "Dgraph adapter for next-auth.",
|
||||
"homepage": "https://next-auth.js.org",
|
||||
"repository": "https://github.com/nextauthjs/adapters",
|
||||
@@ -38,6 +38,7 @@
|
||||
"@types/jsonwebtoken": "^8.5.5",
|
||||
"@types/node-fetch": "^2.5.11",
|
||||
"jest": "^27.0.6",
|
||||
"next-auth": "^4.0.1",
|
||||
"ts-jest": "^27.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"extends": "tsconfig/base.json",
|
||||
"extends": "tsconfig/adapters.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "dist"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@next-auth/dynamodb-adapter",
|
||||
"repository": "https://github.com/nextauthjs/adapters",
|
||||
"version": "1.0.2",
|
||||
"version": "1.0.3",
|
||||
"description": "AWS DynamoDB adapter for next-auth.",
|
||||
"keywords": [
|
||||
"next-auth",
|
||||
@@ -37,6 +37,7 @@
|
||||
"devDependencies": {
|
||||
"@aws-sdk/client-dynamodb": "^3.36.1",
|
||||
"@aws-sdk/lib-dynamodb": "^3.36.1",
|
||||
"@shelf/jest-dynamodb": "^2.1.0"
|
||||
"@shelf/jest-dynamodb": "^2.1.0",
|
||||
"next-auth": "^4.0.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"extends": "tsconfig/base.json",
|
||||
"extends": "tsconfig/adapters.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "dist"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@next-auth/fauna-adapter",
|
||||
"version": "1.0.2",
|
||||
"version": "1.0.3",
|
||||
"description": "Fauna Adapter for NextAuth",
|
||||
"homepage": "https://next-auth.js.org",
|
||||
"repository": "https://github.com/nextauthjs/adapters",
|
||||
@@ -37,7 +37,8 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@fauna-labs/fauna-schema-migrate": "^2.1.3",
|
||||
"faunadb": "^4.3.0"
|
||||
"faunadb": "^4.3.0",
|
||||
"next-auth": "^4.0.1"
|
||||
},
|
||||
"jest": {
|
||||
"preset": "adapter-test/jest"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"extends": "tsconfig/base.json",
|
||||
"extends": "tsconfig/adapters.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "dist"
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
"access": "public"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"build:wip": "tsc",
|
||||
"test:wip": "FIRESTORE_EMULATOR_HOST=localhost:8080 firebase emulators:exec --only firestore --project next-auth-test jest"
|
||||
},
|
||||
"peerDependencies": {
|
||||
@@ -37,6 +37,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"firebase": "^8.6.2",
|
||||
"firebase-tools": "^9.11.0"
|
||||
"firebase-tools": "^9.11.0",
|
||||
"next-auth": "^4.0.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@next-auth/mikro-orm-adapter",
|
||||
"version": "1.0.1",
|
||||
"version": "2.0.1",
|
||||
"description": "MikroORM adapter for next-auth.",
|
||||
"homepage": "https://next-auth.js.org",
|
||||
"repository": "https://github.com/nextauthjs/adapters",
|
||||
@@ -37,7 +37,8 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@mikro-orm/core": "^5.0.2",
|
||||
"@mikro-orm/sqlite": "^5.0.2"
|
||||
"@mikro-orm/sqlite": "^5.0.2",
|
||||
"next-auth": "^4.0.1"
|
||||
},
|
||||
"jest": {
|
||||
"preset": "adapter-test/jest"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"extends": "tsconfig/base.json",
|
||||
"extends": "tsconfig/adapters.json",
|
||||
"compilerOptions": {
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@next-auth/mongodb-adapter",
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.3",
|
||||
"description": "mongoDB adapter for next-auth.",
|
||||
"homepage": "https://next-auth.js.org",
|
||||
"repository": "https://github.com/nextauthjs/adapters",
|
||||
@@ -26,7 +26,8 @@
|
||||
"next-auth": "^4.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"mongodb": "^4.4.0"
|
||||
"mongodb": "^4.4.0",
|
||||
"next-auth": "^4.0.1"
|
||||
},
|
||||
"jest": {
|
||||
"preset": "adapter-test/jest"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"extends": "tsconfig/base.json",
|
||||
"extends": "tsconfig/adapters.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "dist"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@next-auth/neo4j-adapter",
|
||||
"version": "1.0.2",
|
||||
"version": "1.0.3",
|
||||
"description": "neo4j adapter for next-auth.",
|
||||
"homepage": "https://next-auth.js.org",
|
||||
"repository": "https://github.com/nextauthjs/adapters",
|
||||
@@ -38,7 +38,8 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/uuid": "^8.3.3",
|
||||
"neo4j-driver": "^4.4.0"
|
||||
"neo4j-driver": "^4.4.0",
|
||||
"next-auth": "^4.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"uuid": "^8.3.2"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"extends": "tsconfig/base.json",
|
||||
"extends": "tsconfig/adapters.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "dist"
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/pouchdb": "^6.4.0",
|
||||
"next-auth": "^4.0.1",
|
||||
"pouchdb": "^7.2.2",
|
||||
"pouchdb-adapter-memory": "^7.2.2",
|
||||
"pouchdb-find": "^7.2.2"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@next-auth/prisma-adapter",
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.3",
|
||||
"description": "Prisma adapter for next-auth.",
|
||||
"homepage": "https://next-auth.js.org",
|
||||
"repository": "https://github.com/nextauthjs/adapters",
|
||||
@@ -33,6 +33,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@prisma/client": "^3.10.0",
|
||||
"next-auth": "^4.0.1",
|
||||
"prisma": "^3.10.0"
|
||||
},
|
||||
"jest": {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"extends": "tsconfig/base.json",
|
||||
"extends": "tsconfig/adapters.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "dist"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@next-auth/sequelize-adapter",
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.2",
|
||||
"description": "Sequelize adapter for next-auth.",
|
||||
"homepage": "https://next-auth.js.org",
|
||||
"repository": "https://github.com/nextauthjs/adapters",
|
||||
@@ -33,6 +33,7 @@
|
||||
"sequelize": "^6.6.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"next-auth": "^4.0.1",
|
||||
"sequelize": "^6.6.5"
|
||||
},
|
||||
"jest": {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"extends": "tsconfig/base.json",
|
||||
"extends": "tsconfig/adapters.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "dist"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@next-auth/typeorm-legacy-adapter",
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.3",
|
||||
"description": "TypeORM (legacy) adapter for next-auth.",
|
||||
"homepage": "https://next-auth.js.org",
|
||||
"repository": "https://github.com/nextauthjs/adapters",
|
||||
@@ -37,6 +37,7 @@
|
||||
"sqlite": "tests/sqlite/test.sh"
|
||||
},
|
||||
"devDependencies": {
|
||||
"next-auth": "^4.0.1",
|
||||
"mssql": "^7.2.1",
|
||||
"mysql": "^2.18.1",
|
||||
"pg": "^8.7.1",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"extends": "tsconfig/base.json",
|
||||
"extends": "tsconfig/adapters.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "dist",
|
||||
|
||||
@@ -29,9 +29,12 @@ npm install next-auth @next-auth/upstash-redis-adapter @upstash/redis
|
||||
```js
|
||||
import NextAuth from "next-auth"
|
||||
import { UpstashRedisAdapter } from "@next-auth/upstash-adapter"
|
||||
import upstashRedisClient from "@upstash/redis"
|
||||
import { Redis } from "@upstash/redis"
|
||||
|
||||
const redis = upstashRedisClient("UPSTASH_REDIS_REST_URL", "UPSTASH_REDIS_REST_TOKEN")
|
||||
const redis = new Redis({
|
||||
url:"UPSTASH_REDIS_REST_URL",
|
||||
token:"UPSTASH_REDIS_REST_TOKEN",
|
||||
})
|
||||
|
||||
// For more information on each option (and a full list of options) go to
|
||||
// https://next-auth.js.org/configuration/options
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@next-auth/upstash-redis-adapter",
|
||||
"version": "1.1.0",
|
||||
"version": "3.0.0",
|
||||
"description": "Upstash adapter for next-auth. It uses Upstash's connectionless (HTTP based) Redis client.",
|
||||
"homepage": "https://next-auth.js.org",
|
||||
"repository": "https://github.com/nextauthjs/adapters",
|
||||
@@ -30,12 +30,13 @@
|
||||
"dist"
|
||||
],
|
||||
"peerDependencies": {
|
||||
"@upstash/redis": "^0.2.1",
|
||||
"@upstash/redis": "^1.0.1",
|
||||
"next-auth": "^4.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@upstash/redis": "^0.2.1",
|
||||
"dotenv": "^10.0.0"
|
||||
"@upstash/redis": "^1.0.1",
|
||||
"dotenv": "^10.0.0",
|
||||
"next-auth": "^4.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"uuid": "^8.3.2"
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import type { Account as AdapterAccount } from "next-auth"
|
||||
import type { Adapter, AdapterUser, AdapterSession } from "next-auth/adapters"
|
||||
import type { Upstash } from "@upstash/redis/src/types"
|
||||
import type {
|
||||
Adapter,
|
||||
AdapterUser,
|
||||
AdapterSession,
|
||||
VerificationToken,
|
||||
} from "next-auth/adapters"
|
||||
import type { Redis } from "@upstash/redis"
|
||||
|
||||
import { v4 as uuid } from "uuid"
|
||||
|
||||
@@ -32,14 +37,15 @@ function isDate(value: any) {
|
||||
return value && isoDateRE.test(value) && !isNaN(Date.parse(value))
|
||||
}
|
||||
|
||||
export function reviveFromJson(json: string) {
|
||||
return JSON.parse(json, (_, value) =>
|
||||
isDate(value) ? new Date(value) : value
|
||||
)
|
||||
export function hydrateDates(json: object) {
|
||||
return Object.entries(json).reduce((acc, [key, val]) => {
|
||||
acc[key] = isDate(val) ? new Date(val as string) : val
|
||||
return acc
|
||||
}, {} as any)
|
||||
}
|
||||
|
||||
export function UpstashRedisAdapter(
|
||||
client: Upstash,
|
||||
client: Redis,
|
||||
options: UpstashRedisAdapterOptions = {}
|
||||
): Adapter {
|
||||
const mergedOptions = {
|
||||
@@ -70,12 +76,15 @@ export function UpstashRedisAdapter(
|
||||
}
|
||||
|
||||
const getAccount = async (id: string) => {
|
||||
const response = await client.get(accountKeyPrefix + id)
|
||||
if (!response.data) return null
|
||||
return reviveFromJson(response.data)
|
||||
const account = await client.get<AdapterAccount>(accountKeyPrefix + id)
|
||||
if (!account) return null
|
||||
return hydrateDates(account)
|
||||
}
|
||||
|
||||
const setSession = async (id: string, session: AdapterSession) => {
|
||||
const setSession = async (
|
||||
id: string,
|
||||
session: AdapterSession
|
||||
): Promise<AdapterSession> => {
|
||||
const sessionKey = sessionKeyPrefix + id
|
||||
await setObjectAsJson(sessionKey, session)
|
||||
await client.set(sessionByUserIdKeyPrefix + session.userId, sessionKey)
|
||||
@@ -83,21 +92,24 @@ export function UpstashRedisAdapter(
|
||||
}
|
||||
|
||||
const getSession = async (id: string) => {
|
||||
const response = await client.get(sessionKeyPrefix + id)
|
||||
if (!response.data) return null
|
||||
return reviveFromJson(response.data)
|
||||
const session = await client.get<AdapterSession>(sessionKeyPrefix + id)
|
||||
if (!session) return null
|
||||
return hydrateDates(session)
|
||||
}
|
||||
|
||||
const setUser = async (id: string, user: AdapterUser) => {
|
||||
const setUser = async (
|
||||
id: string,
|
||||
user: AdapterUser
|
||||
): Promise<AdapterUser> => {
|
||||
await setObjectAsJson(userKeyPrefix + id, user)
|
||||
await client.set(`${emailKeyPrefix}${user.email as string}`, id)
|
||||
return user
|
||||
}
|
||||
|
||||
const getUser = async (id: string) => {
|
||||
const response = await client.get(userKeyPrefix + id)
|
||||
if (!response.data) return null
|
||||
return reviveFromJson(response.data)
|
||||
const user = await client.get<AdapterUser>(userKeyPrefix + id)
|
||||
if (!user) return null
|
||||
return hydrateDates(user)
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -110,9 +122,11 @@ export function UpstashRedisAdapter(
|
||||
},
|
||||
getUser,
|
||||
async getUserByEmail(email) {
|
||||
const emailResponse = await client.get(emailKeyPrefix + email)
|
||||
if (!emailResponse.data) return null
|
||||
return await getUser(emailResponse.data)
|
||||
const userId = await client.get<string>(emailKeyPrefix + email)
|
||||
if (!userId) {
|
||||
return null
|
||||
}
|
||||
return await getUser(userId)
|
||||
},
|
||||
async getUserByAccount(account) {
|
||||
const dbAccount = await getAccount(
|
||||
@@ -124,7 +138,7 @@ export function UpstashRedisAdapter(
|
||||
async updateUser(updates) {
|
||||
const userId = updates.id as string
|
||||
const user = await getUser(userId)
|
||||
return await setUser(userId, { ...user, ...updates })
|
||||
return await setUser(userId, { ...(user as AdapterUser), ...updates })
|
||||
},
|
||||
async linkAccount(account) {
|
||||
const id = `${account.provider}:${account.providerAccountId}`
|
||||
@@ -158,10 +172,13 @@ export function UpstashRedisAdapter(
|
||||
},
|
||||
async useVerificationToken(verificationToken) {
|
||||
const tokenKey = verificationTokenKeyPrefix + verificationToken.identifier
|
||||
const tokenResponse = await client.get(tokenKey)
|
||||
if (!tokenResponse.data) return null
|
||||
|
||||
const token = await client.get<VerificationToken>(tokenKey)
|
||||
if (!token) return null
|
||||
|
||||
await client.del(tokenKey)
|
||||
return reviveFromJson(tokenResponse.data)
|
||||
return hydrateDates(token)
|
||||
// return reviveFromJson(token)
|
||||
},
|
||||
async unlinkAccount(account) {
|
||||
const id = `${account.provider}:${account.providerAccountId}`
|
||||
@@ -177,17 +194,15 @@ export function UpstashRedisAdapter(
|
||||
const user = await getUser(userId)
|
||||
if (!user) return
|
||||
const accountByUserKey = accountByUserIdPrefix + userId
|
||||
const accountRequest = await client.get(accountByUserKey)
|
||||
const accountKey = accountRequest.data
|
||||
const accountKey = await client.get<string>(accountByUserKey)
|
||||
const sessionByUserIdKey = sessionByUserIdKeyPrefix + userId
|
||||
const sessionRequest = await client.get(sessionByUserIdKey)
|
||||
const sessionKey = sessionRequest.data
|
||||
const sessionKey = await client.get<string>(sessionByUserIdKey)
|
||||
await client.del(
|
||||
userKeyPrefix + userId,
|
||||
`${emailKeyPrefix}${user.email as string}`,
|
||||
accountKey,
|
||||
accountKey as string,
|
||||
accountByUserKey,
|
||||
sessionKey,
|
||||
sessionKey as string,
|
||||
sessionByUserIdKey
|
||||
)
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import upstashRedisClient from "@upstash/redis"
|
||||
import { Redis } from "@upstash/redis"
|
||||
import { runBasicTests } from "adapter-test"
|
||||
import { reviveFromJson, UpstashRedisAdapter } from "../src"
|
||||
import { hydrateDates, UpstashRedisAdapter } from "../src"
|
||||
import "dotenv/config"
|
||||
|
||||
if (!process.env.UPSTASH_REDIS_URL || !process.env.UPSTASH_REDIS_KEY) {
|
||||
@@ -8,35 +8,39 @@ if (!process.env.UPSTASH_REDIS_URL || !process.env.UPSTASH_REDIS_KEY) {
|
||||
expect(true).toBe(true)
|
||||
})
|
||||
} else {
|
||||
const client = upstashRedisClient(
|
||||
process.env.UPSTASH_REDIS_URL,
|
||||
process.env.UPSTASH_REDIS_KEY
|
||||
)
|
||||
const client = new Redis({
|
||||
url: process.env.UPSTASH_REDIS_URL,
|
||||
token: process.env.UPSTASH_REDIS_KEY,
|
||||
})
|
||||
|
||||
runBasicTests({
|
||||
adapter: UpstashRedisAdapter(client, { baseKeyPrefix: "testApp:" }),
|
||||
db: {
|
||||
async user(id: string) {
|
||||
const { data } = await client.get(`testApp:user:${id}`)
|
||||
return reviveFromJson(data)
|
||||
const data = await client.get<object>(`testApp:user:${id}`)
|
||||
if (!data) return null
|
||||
return hydrateDates(data)
|
||||
},
|
||||
async account({ provider, providerAccountId }) {
|
||||
const { data } = await client.get(
|
||||
const data = await client.get<object>(
|
||||
`testApp:user:account:${provider}:${providerAccountId}`
|
||||
)
|
||||
return reviveFromJson(data)
|
||||
if (!data) return null
|
||||
return hydrateDates(data)
|
||||
},
|
||||
async session(sessionToken) {
|
||||
const { data } = await client.get(
|
||||
const data = await client.get<object>(
|
||||
`testApp:user:session:${sessionToken}`
|
||||
)
|
||||
return reviveFromJson(data)
|
||||
if (!data) return null
|
||||
return hydrateDates(data)
|
||||
},
|
||||
async verificationToken(where) {
|
||||
const { data } = await client.get(
|
||||
const data = await client.get<object>(
|
||||
`testApp:user:token:${where.identifier}`
|
||||
)
|
||||
return reviveFromJson(data)
|
||||
if (!data) return null
|
||||
return hydrateDates(data)
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"extends": "tsconfig/base.json",
|
||||
"extends": "tsconfig/adapters.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "dist"
|
||||
|
||||
@@ -77,8 +77,7 @@ NextAuth.js can be used with or without a database.
|
||||
- Designed to be secure by default and encourage best practices for safeguarding user data
|
||||
- Uses Cross-Site Request Forgery (CSRF) Tokens on POST routes (sign in, sign out)
|
||||
- Default cookie policy aims for the most restrictive policy appropriate for each cookie
|
||||
- When JSON Web Tokens are enabled, they are signed by default (JWS) with HS512
|
||||
- Use JWT encryption (JWE) by setting the option `encryption: true` (defaults to A256GCM)
|
||||
- When JSON Web Tokens are enabled, they are encrypted by default (JWE) with A256GCM
|
||||
- Auto-generates symmetric signing and encryption keys for developer convenience
|
||||
- Features tab/window syncing and session polling to support short lived sessions
|
||||
- Attempts to implement the latest guidance published by [Open Web Application Security Project](https://owasp.org)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "next-auth",
|
||||
"version": "4.2.1",
|
||||
"version": "4.3.3",
|
||||
"description": "Authentication for Next.js",
|
||||
"homepage": "https://next-auth.js.org",
|
||||
"repository": "https://github.com/nextauthjs/next-auth.git",
|
||||
|
||||
@@ -107,8 +107,8 @@ export async function NextAuthHandler<
|
||||
if (pages.signIn) {
|
||||
let signinUrl = `${pages.signIn}${
|
||||
pages.signIn.includes("?") ? "&" : "?"
|
||||
}callbackUrl=${options.callbackUrl}`
|
||||
if (error) signinUrl = `${signinUrl}&error=${error}`
|
||||
}callbackUrl=${encodeURIComponent(options.callbackUrl)}`
|
||||
if (error) signinUrl = `${signinUrl}&error=${encodeURIComponent(error)}`
|
||||
return { redirect: signinUrl, cookies }
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ export const defaultCallbacks: CallbacksOptions = {
|
||||
return true
|
||||
},
|
||||
redirect({ url, baseUrl }) {
|
||||
if (url.startsWith(baseUrl)) return url
|
||||
else if (url.startsWith("/")) return new URL(url, baseUrl).toString()
|
||||
if (url.startsWith("/")) return `${baseUrl}${url}`
|
||||
else if (new URL(url).origin === baseUrl) return url
|
||||
return baseUrl
|
||||
},
|
||||
session({ session }) {
|
||||
|
||||
@@ -36,6 +36,7 @@ export function oAuth1Client(options: InternalOptions<"oauth">) {
|
||||
// Promisify getOAuth1AccessToken() for OAuth1
|
||||
const originalGetOAuth1AccessToken =
|
||||
oauth1Client.getOAuthAccessToken.bind(oauth1Client)
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
oauth1Client.getOAuthAccessToken = async (...args: any[]) => {
|
||||
return await new Promise((resolve, reject) => {
|
||||
originalGetOAuth1AccessToken(
|
||||
@@ -52,6 +53,7 @@ export function oAuth1Client(options: InternalOptions<"oauth">) {
|
||||
|
||||
const originalGetOAuthRequestToken =
|
||||
oauth1Client.getOAuthRequestToken.bind(oauth1Client)
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
oauth1Client.getOAuthRequestToken = async (params = {}) => {
|
||||
return await new Promise((resolve, reject) => {
|
||||
originalGetOAuthRequestToken(
|
||||
|
||||
@@ -123,10 +123,11 @@ export default function SigninPage(props: SignInServerPageParams) {
|
||||
<input
|
||||
id={`input-email-for-${provider.id}-provider`}
|
||||
autoFocus
|
||||
type="text"
|
||||
type="email"
|
||||
name="email"
|
||||
value={email}
|
||||
placeholder="email@example.com"
|
||||
required
|
||||
/>
|
||||
<button type="submit">Sign in with {provider.name}</button>
|
||||
</form>
|
||||
|
||||
@@ -43,9 +43,9 @@ body {
|
||||
background-color: var(--color-background);
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans,
|
||||
sans-serif, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif,
|
||||
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
|
||||
"Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif,
|
||||
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
}
|
||||
|
||||
h1 {
|
||||
|
||||
@@ -99,7 +99,7 @@ export function proxyLogger(
|
||||
}
|
||||
;(metadata as any).client = true
|
||||
const url = `${basePath}/_log`
|
||||
const body = new URLSearchParams({ level, code, ...metadata })
|
||||
const body = new URLSearchParams({ level, code, ...(metadata as any) })
|
||||
if (navigator.sendBeacon) {
|
||||
return navigator.sendBeacon(url, body)
|
||||
}
|
||||
|
||||
@@ -82,9 +82,9 @@ export default function Kakao<P extends Record<string, any> = KakaoProfile>(
|
||||
profile(profile) {
|
||||
return {
|
||||
id: profile.id,
|
||||
name: profile.kakao_account?.profile.nickname,
|
||||
name: profile.kakao_account?.profile?.nickname,
|
||||
email: profile.kakao_account?.email,
|
||||
image: profile.kakao_account?.profile.profile_image_url,
|
||||
image: profile.kakao_account?.profile?.profile_image_url,
|
||||
}
|
||||
},
|
||||
options,
|
||||
|
||||
@@ -359,6 +359,12 @@ export function SessionProvider(props: SessionProviderProps) {
|
||||
}
|
||||
|
||||
__NEXTAUTH._getSession()
|
||||
|
||||
return () => {
|
||||
__NEXTAUTH._lastSync = 0
|
||||
__NEXTAUTH._session = undefined
|
||||
__NEXTAUTH._getSession = () => {}
|
||||
}
|
||||
}, [])
|
||||
|
||||
React.useEffect(() => {
|
||||
|
||||
@@ -2,6 +2,17 @@
|
||||
"extends": "tsconfig/base.json",
|
||||
"compilerOptions": {
|
||||
"emitDeclarationOnly": true,
|
||||
"strictNullChecks": true,
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"strict": false,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "react-jsx",
|
||||
"stripInternal": true,
|
||||
"skipDefaultLibCheck": true,
|
||||
"baseUrl": ".",
|
||||
"outDir": "."
|
||||
},
|
||||
|
||||
8
packages/tsconfig/adapters.json
Normal file
8
packages/tsconfig/adapters.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "./base.json",
|
||||
"compilerOptions": {
|
||||
"target": "ES2019",
|
||||
"module": "commonjs",
|
||||
"strict": true
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,9 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"strictNullChecks": true,
|
||||
"target": "es2019",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"strict": false,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "react-jsx",
|
||||
"declaration": true,
|
||||
"stripInternal": true,
|
||||
"skipLibCheck": true,
|
||||
"skipDefaultLibCheck": true
|
||||
"skipLibCheck": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createHash } from "crypto"
|
||||
import type { Commit, PackageToRelease } from "./types"
|
||||
|
||||
import { debug, pkgJson, execSync } from "./utils"
|
||||
@@ -17,13 +18,16 @@ export async function publish(options: {
|
||||
`Dry run, npm publish for package ${pkg.name} will show the wrong version (${pkg.oldVersion}). In normal run, it would be ${pkg.newVersion}`
|
||||
)
|
||||
} else {
|
||||
console.log(`Writing version to package.json for package ${pkg.name}`)
|
||||
console.log(
|
||||
`Writing version ${pkg.newVersion} to package.json for package ${pkg.name}`
|
||||
)
|
||||
await pkgJson.update(pkg.path, { version: pkg.newVersion })
|
||||
console.log("package.json file has been written, publishing...")
|
||||
}
|
||||
|
||||
let npmPublish = `npm publish --access public --registry=https://registry.npmjs.org`
|
||||
// We use different tokens for `next-auth` and `@next-auth/*` packages
|
||||
|
||||
if (pkg.name === "next-auth") {
|
||||
process.env.NPM_TOKEN = process.env.NPM_TOKEN_PKG
|
||||
} else {
|
||||
@@ -34,12 +38,13 @@ export async function publish(options: {
|
||||
console.log(`Dry run, skip npm publish for package ${pkg.name}...`)
|
||||
npmPublish += " --dry-run"
|
||||
} else {
|
||||
const npmrc =
|
||||
'echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> .npmrc'
|
||||
execSync(npmrc, { cwd: pkg.path })
|
||||
execSync(
|
||||
"echo '//registry.npmjs.org/:_authToken=${NPM_TOKEN}' > .npmrc",
|
||||
{ cwd: pkg.path }
|
||||
)
|
||||
}
|
||||
|
||||
execSync(npmPublish, { cwd: pkg.path })
|
||||
execSync(`${npmPublish} --no-workspaces`, { cwd: pkg.path })
|
||||
}
|
||||
|
||||
if (dryRun) {
|
||||
@@ -63,6 +68,7 @@ export async function publish(options: {
|
||||
} else {
|
||||
console.log(`Creating git tag...`)
|
||||
execSync(`git tag ${gitTag}`)
|
||||
execSync("git push --tags")
|
||||
console.log(`Creating GitHub release notes...`)
|
||||
execSync(`gh release create ${gitTag} --notes '${changelog}'`)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user