Compare commits

..

15 Commits

Author SHA1 Message Date
Balázs Orbán
d29e3e9c9d Merge branch 'main'
Conflicts:
	config/babel.config.json
	package-lock.json
	package.json
	src/server/index.js
	src/server/routes/callback.js
	src/server/routes/signin.js
2021-06-09 02:16:11 +02:00
Balázs Orbán
a388b44d0b Merge branch 'main' into next 2021-05-03 21:11:04 +02:00
Balázs Orbán
b6a3a72db4 Merge branch 'main' into next 2021-04-24 23:20:41 +02:00
Balázs Orbán
edcb10a823 Merge branch 'main' into next 2021-04-23 15:43:20 +02:00
Balázs Orbán
2acabe19e0 Merge main into next 2021-04-23 15:28:26 +02:00
Balázs Orbán
a6f5f4c184 fix: use upgraded require optional (#1743)
* chore(deps): switch back to (updated) require_optional

* fix: use @balazsorban/require-optional
2021-04-16 16:05:44 +02:00
Balázs Orbán
9fa93e3b5e fix(build): use optional-require dependency (#1736)
* chore(deps): add optional-require

* refactor: use optional-require
2021-04-16 00:23:29 +02:00
Balázs Orbán
cb4342fdda feat(build): modernize how we bundle next-auth (#1682)
* feat(build): optionally include TypeORM

If the user doesn't use databases,
it shouldn't be necessary to iclude it in the bundle.
This can more than half the package size!

* feat(build): clean up in dependencies

Remove unused dependencies, move optional ones to be optional

* feat(build): add exports field

* fix: use peerDependenciesMeta instead of non-standard peerOptionalDependecns field

* fix: ts-standard string quotes

* fix: ts-standard string quotes

* refactor: use asnyc/await for sendVerificationRequest

* chore(deps): upgrade mongodb, remove require_optional

Co-authored-by: ndom91 <yo@ndo.dev>

BREAKING CHANGE:
`typeorm`, and `nodemailer` are no longer dependencies added by default.
If you need any of them, you will have to install them yourself in your project directory.
TypeOrm is the default adapter, so if you only provide an `adapter` configuration or a `database`, you will need `typeorm`. You could also check out `@next-auth/typeorm-adapter`. In case you are using the Email provider, you will have to install `nodemailer` (or you can use the choice of your library in the `sendVerificationRequest` callback to send out the e-mail.)
2021-04-15 23:40:33 +02:00
Balázs Orbán
5f717b3914 chore: merge main into next 2021-04-12 00:46:27 +02:00
Balázs Orbán
d09a45ec7c chore: merge main into next 2021-03-26 16:23:35 +01:00
Balázs Orbán
930f58eba3 chore: merge main into next 2021-03-08 01:05:54 +01:00
Balázs Orbán
c20b7f2930 feat: use IE11 as client code bundle target (#1402) 2021-03-03 20:25:42 +01:00
Balázs Orbán
e418cddd96 chore: merge main into next 2021-03-03 20:25:42 +01:00
Balázs Orbán
111e7aabdf feat(provider): remove state property
BREAKING CHANGE: adding `state: true` is already redundant
as `protection: "state` is the default value. `state: false`
can be substituted with `protection: "state"`
2021-02-15 21:47:47 +01:00
Balázs Orbán
a113ef6fab feat: encourage returning strings instead of throwing
BREAKING CHANGE: We have supported throwing strings
for redirections, while we were showing a waring.
From now on, it is not possible. The user MUST return a string,
rather than throw it.
2021-02-15 21:47:35 +01:00
89 changed files with 16077 additions and 7669 deletions

1
.github/FUNDING.yml vendored
View File

@@ -1,4 +1,3 @@
# https://docs.github.com/en/github/administering-a-repository/displaying-a-sponsor-button-in-your-repository
open_collective: nextauth
github: [balazsorban44]

43
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,43 @@
---
name: Bug report
about: Report a defect with NextAuth.js
labels: bug
assignees: ""
---
## Description 🐜
Please provide a clear and concise description of the bug in NextAuth.js.
🚧 _Do not report bugs with your own project here; ask for help [by raising a question instead](https://github.com/nextauthjs/next-auth/issues/new?assignees=&labels=question&template=question.md) - this helps us a lot with administration overhead._
## How to reproduce ☕️
We encourage you to use one of the templates set up on **CodeSandbox** to reproduce your issue:
- [`next-auth-example`](https://codesandbox.io/s/next-auth-example-1kktb)
- [`next-auth-typescript-example`](https://codesandbox.io/s/next-auth-typescript-example-se32w)
🚧 _If you don't provide any way to reproduce the bug, the issue is at risk of being closed._
## Screenshots / Logs 📽
**Help us help you**. We can address the bug you found much faster if you provide contextual screenshots or screen recordings showcasing the issue.
See [Kap](https://getkap.co/) for a good, easy-to-use, cross-platform screen recording tool.
## Environment 🖥
Please run this command:
```
$ npx envinfo --system --binaries --browsers --npmPackages "{next-auth}"
```
and paste the output here.
## Contributing 🙌🏽
It takes a lot of work 🏋🏻‍♀️ maintaining a library like `next-auth`; any contribution is more than welcome 💚
In case you're willing to help fix this bug, please let us know here, and we'll reach you 😊 . Otherwise, you can have a look at the issues labelled with [`"good first issue"`](https://github.com/nextauthjs/next-auth/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) and pick any of them.

View File

@@ -1,91 +0,0 @@
name: Bug Report
description: File a bug report
labels: bug
# note: markdown sections will NOT appear as part of the issue as per documentation, rather they provide context to the user
# https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema#markdown
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report! Please provide the following information:
- type: textarea
id: description
attributes:
label: Description 🐜
description: Please provide a clear and concise description of the bug in NextAuth.js
validations:
required: true
- type: dropdown
id: ownproject
attributes:
label: Is this a bug in your own project?
description: 🚧 _Do not report bugs with your own project here; ask for help [by raising a question instead](https://github.com/nextauthjs/next-auth/issues/new?assignees=&labels=question&template=question.md) or use the [Discussions tab](https://github.com/nextauthjs/next-auth/discussions) - this helps us reduce the maintenance overhead._
multiple: false
options:
- "Yes"
- "No"
validations:
required: true
- type: textarea
id: reproduction
attributes:
label: How to reproduce ☕️
description: Please provide a link or code snippets to a minimal reproduction of the bug
validations:
required: true
- type: markdown
attributes:
value: |
We encourage you to use one of the templates set up on **CodeSandbox** to reproduce your issue:
- [`next-auth-example`](https://codesandbox.io/s/next-auth-example-1kktb)
- [`next-auth-typescript-example`](https://codesandbox.io/s/next-auth-typescript-example-se32w)
🚧 _If you don't provide any way to reproduce the bug, the issue is at risk of being closed._
- type: textarea
id: logs
attributes:
label: Screenshots / Logs 📽
description: We can address the bug you found much faster if you provide contextual screenshots or screen recordings showcasing the issue.
- type: markdown
attributes:
value: |
See [Kap](https://getkap.co/) for a good, easy-to-use, cross-platform screen recording tool.
validations:
required: false
- type: textarea
id: environment
attributes:
label: Environment 🖥
validations:
required: true
- type: markdown
attributes:
value: |
Please run this command in your project's root folder:
```sh
npx envinfo --system --binaries --browsers --npmPackages "next,next-auth,react"
```
- type: dropdown
id: pr
attributes:
label: Contributing 🙌🏽
multiple: false
options:
- "Yes, I am willing to help solve this bug in a PR"
- "No, I am afraid I cannot help regarding this"
validations:
required: true
- type: markdown
attributes:
value: |
It takes a lot of work 🏋🏻‍♀️ maintaining a library like `next-auth`; any contribution is more than welcome 💚

View File

@@ -0,0 +1,39 @@
---
name: Feature request
about: Suggest an idea for NextAuth.js
labels: enhancement
assignees: ""
---
## Summary 💭
A clear and concise summary of the feature being proposed.
## Description 📓
Please provide a more in-depth description of the feature proposed.
Make sure you provide plenty of [links]() to external documentation and inline code examples like so:
```js
function myAwesomeNextAuthFeature() {
return 💚
}
```
Take time thinking about what you want to say and help us understand your proposal making sure that this description contains:
- **purpose of the feature**
- **potential problems**
- **potential alternatives**
You can use one of the templates set up on **CodeSandbox** to better illustrate your idea:
- [`next-auth-example`](https://codesandbox.io/s/next-auth-example-1kktb)
- [`next-auth-typescript-example`](https://codesandbox.io/s/next-auth-typescript-example-se32w)
## Contributing 🙌🏽
It takes a lot of work 🏋🏻‍♀️ maintaining a library like `next-auth`; any contribution is more than welcome 💚
In case you're willing to help implement this feature, please let us know here, and we'll reach you 😊 . Otherwise, you can have a look at the issues labelled with [`"good first issue"`](https://github.com/nextauthjs/next-auth/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) and pick any of them.

View File

@@ -1,68 +0,0 @@
name: Feature Request
description: Suggest an idea for NextAuth.js
labels: enhancement
# note: markdown sections will NOT appear as part of the issue as per documentation, rather they provide context to the user
# https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema#markdown
body:
- type: markdown
attributes:
value: |
Thank you very much for reaching out to us regarding the awesome feature that you believe should be included in the NextAuth.js library. Please provide the following information:
- type: textarea
id: description
attributes:
label: Description 📓
description: Please provide a more in-depth description of the feature proposed.
validations:
required: true
- type: markdown
attributes:
value: |
Make sure you provide plenty of [links]() to external documentation and inline code examples like so:
```js
function myAwesomeNextAuthFeature() {
return 💚
}
```
Take time thinking about what you want to say and help us understand your proposal making sure that this description contains:
- **purpose of the feature**
- **potential problems**
- **potential alternatives**
- type: textarea
id: reproduction
attributes:
label: How to reproduce ☕️
description: If you have a CodeSandbox playground or some code snippets to help us visualize your idea better, please provide it here.
validations:
required: true
- type: markdown
attributes:
value: |
You can use one of the templates set up on **CodeSandbox** to better illustrate your idea:
- [`next-auth-example`](https://codesandbox.io/s/next-auth-example-1kktb)
- [`next-auth-typescript-example`](https://codesandbox.io/s/next-auth-typescript-example-se32w)
- type: dropdown
id: pr
attributes:
label: Contributing 🙌🏽
multiple: false
options:
- "Yes, I am willing to help implement this feature in a PR"
- "No, I am afraid I cannot help regarding this"
validations:
required: true
- type: markdown
attributes:
value: |
It takes a lot of work 🏋🏻‍♀️ maintaining a library like `next-auth`; any contribution is more than welcome 💚

32
.github/ISSUE_TEMPLATE/question.md vendored Normal file
View File

@@ -0,0 +1,32 @@
---
name: Question
about: Ask a question about NextAuth.js or for help using it
labels: question
assignees: ""
---
## Question 💬
Please provide an in-depth description of the question you have.
Make sure you [link]() to external documentation if necessary and provide inline code examples like so:
```js
function myAwesomeNextAuthFeature() {
return 💚
}
```
**NOTE:** Questions will be converted to Discussions. You can find them [here](https://github.com/nextauthjs/next-auth/discussions)!
## How to reproduce ☕️
We encourage you to use the template set-up on **CodeSandbox** as a playground to represent your question or doubt:
- [`next-auth-example`](https://codesandbox.io/s/next-auth-example-1kktb)
## Contributing 🙌🏽
It takes a lot of work 🏋🏻‍♀️ maintaining a library like `next-auth`; any contribution is more than welcome 💚
In case you're willing to help answer this question, please let us know here, and we'll reach you 😊 . Otherwise, you can have a look at the issues labelled with [`"good first issue"`](https://github.com/nextauthjs/next-auth/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) and pick any of them.

View File

@@ -1,62 +0,0 @@
name: Question
description: Ask a question about NextAuth.js or for help using it
labels: question
# note: markdown sections will NOT appear as part of the issue as per documentation, rather they provide context to the user
# https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema#markdown
body:
- type: markdown
attributes:
value: |
We are glad that you have a question about this library. Please provide the following information:
- type: textarea
id: question
attributes:
label: Question 💬
description: Please provide an in-depth description of the question you have.
validations:
required: true
- type: markdown
attributes:
value: |
Make sure you [link]() to external documentation if necessary and provide inline code examples like so:
```js
function myAwesomeNextAuthFeature() {
return 💚
}
```
**NOTE:** Questions will be converted to Discussions. You can find them [here](https://github.com/nextauthjs/next-auth/discussions)!
- type: textarea
id: reproduction
attributes:
label: How to reproduce ☕️
description: Please provide a link to a minimal reproduction or code snippets that represents your question
validations:
required: true
- type: markdown
attributes:
value: |
We encourage you to use the template set-up on **CodeSandbox** as a playground to represent your question or doubt:
- [`next-auth-example`](https://codesandbox.io/s/next-auth-example-1kktb)
- type: dropdown
id: pr
attributes:
label: Contributing 🙌🏽
multiple: false
options:
- "Yes, I am willing to help answer this question in a PR"
- "No, I am afraid I cannot help regarding this"
validations:
required: true
- type: markdown
attributes:
value: |
It takes a lot of work 🏋🏻‍♀️ maintaining a library like `next-auth`; any contribution is more than welcome 💚

36
.github/ISSUE_TEMPLATE/typescript.md vendored Normal file
View File

@@ -0,0 +1,36 @@
---
name: TypeScript
about: Ask a question about NextAuth.js TypeScript integration
labels:
- question
- TypeScript
assignees:
- lluia
- balazsorban44
---
## Question 💬
Please provide an in-depth description of the question you have when using NextAuth.js on a Typescript project or when consuming the built-in types for `next-auth`.
Make sure you [link]() to external documentation if necessary and provide inline code examples like so:
```js
function myAwesomeNextAuthFeature() {
return 💚
}
```
**NOTE:** Questions will be converted to Discussions. You can find them [here](https://github.com/nextauthjs/next-auth/discussions)!
## How to reproduce ☕️
We encourage you to use the template set-up on **CodeSandbox** as a playground to represent your question or doubt:
- [`next-auth-typescript-example`](https://codesandbox.io/s/next-auth-typescript-example-se32w)
## Contributing 🙌🏽
It takes a lot of work 🏋🏻‍♀️ maintaining a library like `next-auth`; any contribution is more than welcome 💚
In case you're willing to help answer this TypeScript question, please let us know here, and we'll reach you 😊 . Otherwise, you can have a look at the issues labelled with [`"good first issue"`](https://github.com/nextauthjs/next-auth/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) and pick any of them.

View File

@@ -1,58 +0,0 @@
name: TypeScript
description: Ask a question about NextAuth.js TypeScript integration
labels: [question, TypeScript]
assignees: [lluia, balazsorban44]
# note: markdown sections will NOT appear as part of the issue as per documentation, rather they provide context to the user
# https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema#markdown
body:
- type: textarea
id: question
attributes:
label: Question 💬
description: Please provide an in-depth description of the question you have when using NextAuth.js on a Typescript project or when consuming the built-in types for `next-auth`.
validations:
required: true
- type: markdown
attributes:
value: |
Make sure you [link]() to external documentation if necessary and provide inline code examples like so:
```js
function myAwesomeNextAuthFeature() {
return 💚
}
```
**NOTE:** Questions will be converted to Discussions. You can find them [here](https://github.com/nextauthjs/next-auth/discussions)!
- type: textarea
id: codesandbox
attributes:
label: How to reproduce ☕️
description: Please provide a link to a minimal reproduction or code snippets that represents your question
validations:
required: true
- type: markdown
attributes:
value: |
We encourage you to use the template set-up on **CodeSandbox** as a playground to represent your question or doubt:
- [`next-auth-typescript-example`](https://codesandbox.io/s/next-auth-typescript-example-se32w)
- type: dropdown
id: pr
attributes:
label: Contributing 🙌🏽
multiple: false
options:
- "Yes, I am willing to help answer this question in a PR"
- "No, I am afraid I cannot help regarding this"
validations:
required: true
- type: markdown
attributes:
value: |
It takes a lot of work 🏋🏻‍♀️ maintaining a library like `next-auth`; any contribution is more than welcome 💚

View File

@@ -1,4 +1,4 @@
name: Release
name: Release Flow
on:
push:
@@ -11,66 +11,32 @@ on:
jobs:
test:
name: Test
name: Tests
runs-on: ubuntu-latest
steps:
- name: Init
uses: actions/checkout@v2
- name: Setup Node
uses: actions/setup-node@v1
with:
node-version: 16
- name: Dependencies
uses: bahmutov/npm-install@v1
- name: Run tests
run: npm test
- name: Build
run: npm run build
- name: Run tests
run: npm test -- --coverage --verbose && npm run test:types
- name: Coverage
uses: codecov/codecov-action@v1
with:
directory: ./coverage
fail_ci_if_error: false
release-branch:
name: Publish branch
runs-on: ubuntu-latest
release:
name: Release
needs: test
if: ${{ github.event_name == 'push' }}
environment: Production
runs-on: ubuntu-latest
steps:
- name: Init
uses: actions/checkout@v2
- name: Setup Node
uses: actions/setup-node@v1
with:
node-version: 16
- name: Dependencies
uses: bahmutov/npm-install@v1
- name: Publish to npm and GitHub
- name: Release
run: npx semantic-release@17
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
release-pr:
name: Publish PR
runs-on: ubuntu-latest
needs: test
if: ${{ github.event_name == 'pull_request' }}
environment: Preview
steps:
- name: Init
uses: actions/checkout@v2
- name: Setup Node
uses: actions/setup-node@v1
with:
node-version: 16
- name: Dependencies
uses: bahmutov/npm-install@v1
- name: Publish to npm
run: |
echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> .npmrc
npm run version:pr
npm publish --access public --tag experimental
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
PR_NUMBER: ${{ github.event.number }}
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
NPM_TOKEN: ${{secrets.NPM_TOKEN}}

8
.gitignore vendored
View File

@@ -59,11 +59,3 @@ app/yarn.lock
# Prisma migrations
/prisma/migrations
# Tests
/coverage
# v4
packages
apps
docs/providers.json

View File

@@ -14,22 +14,22 @@ appearance, race, religion, or sexual identity and orientation.
Examples of behavior that contributes to creating a positive environment
include:
- Using welcoming and inclusive language
- Being respectful of differing viewpoints and experiences
- Gracefully accepting constructive criticism
- Focusing on what is best for the community
- Showing empathy towards other community members
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
- The use of sexualized language or imagery and unwelcome sexual attention or
advances
- Trolling, insulting/derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or electronic
address, without explicit permission
- Other conduct which could reasonably be considered inappropriate in a
professional setting
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
@@ -55,11 +55,11 @@ further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting me@iaincollins.com or info@balazsorban.com and yo@ndo.dev.
All complaints will be reviewed and investigated and will result in a response
that is deemed necessary and appropriate to the circumstances. The project team
is obligated to maintain confidentiality with regard to the reporter of an
incident. Further details of specific enforcement policies may be posted separately.
reported by contacting me@iaincollins.com. All complaints will be reviewed and
investigated and will result in a response that is deemed necessary and
appropriate to the circumstances. The project team is obligated to maintain
confidentiality with regard to the reporter of an incident. Further details of
specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other

View File

@@ -25,8 +25,6 @@ Anyone can be a contributor. Either you found a typo, or you have an awesome fea
A quick guide on how to setup _next-auth_ locally to work on it and test out any changes:
The dev application requires you to use `npm@7`.
1. Clone the repo:
```sh
@@ -34,10 +32,10 @@ git clone git@github.com:nextauthjs/next-auth.git
cd next-auth
```
2. Install packages, set up the dev application:
2. Install packages:
```sh
npm run dev:setup
npm i && npm run dev:setup
```
3. Populate `.env.local`:
@@ -80,12 +78,26 @@ You can look at the existing built-in providers for inspiration.
#### Databases
If you would like to contribute to an existing database adapter or help create a new one, head over to the [nextauthjs/adapters](https://www.github.com/nextauthjs/adapters) repository and follow the instructions provided there.
Included is a Docker Compose file that starts up MySQL, PostgreSQL, and MongoDB databases on localhost.
It will use port `3306`, `5432`, and `27017` on localhost respectively; please make sure those ports are not used by other services on localhost.
You can start them with `npm run db:start` and stop them with `npm run db:stop`.
You will need Docker and Docker Compose installed to be able to start / stop the databases.
When stopping the databases, it will reset their contents.
#### Testing
Tests can be run with `npm run test`.
Automated tests are currently crude and limited in functionality, but improvements are in development.
Currently, to run tests you need to first have started local test databases (e.g. using `npm run db:start`).
The databases can take a few seconds to start up, so you might need to give it a minute before running the tests.
## For maintainers
We use [semantic-release](https://github.com/semantic-release/semantic-release) together with [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0) to automate releases. This makes the maintenance process easier and less error-prone to human error. Please study the "Conventional Commits" site to understand how to write a good commit message.

View File

@@ -147,43 +147,13 @@ export default function Component() {
<img width="500px" src="https://contrib.rocks/image?repo=nextauthjs/next-auth" />
</a>
<div>
<a href="https://vercel.com?utm_source=nextauthjs&utm_campaign=oss"></a>
<a href="https://vercel.com?utm_source=nextauthjs&utm_campaign=oss">
<img width="170px" src="https://raw.githubusercontent.com/nextauthjs/next-auth/canary/www/static/img/powered-by-vercel.svg" alt="Powered By Vercel" />
</a>
</div>
<div>
<p align="left">Thanks to Vercel sponsoring this project by allowing it to be deployed for free for the entire NextAuth.js Team</p>
</div>
### Support
We're happy to announce we've recently created an [OpenCollective](https://opencollective.org/nextauth) for individuals and companies looking to contribute financially to the project!
<!--sponsors start-->
<table>
<tbody>
<tr>
<td align="center" valign="top">
<a href="https://vercel.com" target="_blank">
<img width="128px" src="https://avatars.githubusercontent.com/u/14985020?v=4" alt="Vercel Logo" />
</a><br />
<div>Vercel</div><br />
<sub>🥉 Bronze Financial Sponsor <br /> ☁️ Infrastructure Support</sub>
</td>
<td align="center" valign="top">
<a href="https://prisma.io" target="_blank">
<img width="128px" src="https://avatars.githubusercontent.com/u/17219288?v=4" alt="Prisma Logo" />
</a><br />
<div>Prisma</div><br />
<sub>🥉 Bronze Financial Sponsor</sub>
</td>
<td align="center" valign="top">
<a href="https://checklyhq.com" target="_blank">
<img width="128px" src="https://avatars.githubusercontent.com/u/25982255?v=4" alt="Checkly Logo" />
</a><br />
<div>Checkly</div><br />
<sub>☁️ Infrastructure Support</sub>
</td>
</tr><tr></tr>
</tbody>
</table>
<br />
<!--sponsors end-->
## Contributing

View File

@@ -19,6 +19,6 @@ If you contact us regarding a serious issue:
- We will disclose the issue (and credit you, with your consent) once a fix to resolve the issue has been released.
- If 90 days has elapsed and we still don't have a fix, we will disclose the issue publicly.
Currently, the best way to report an issue is by contacting us via email at me@iaincollins.com or info@balazsorban.com and yo@ndo.dev.
Currently, the best way to report an issue is by emailing me@iaincollins.com
For less serious issues (e.g. RFC compliance for unsupported flows or potential issues that may cause a problem future or default behaviour / options) it is appropriate to submit these these publically as bug reports or feature requests or to raise a question to open a discussion around them.

View File

@@ -23,6 +23,8 @@ TWITTER_SECRET=
EMAIL_SERVER=smtps://user@gmail.com:password@smtp.gmail.com:465
EMAIL_FROM=user@gmail.com
# You can use any of these as the "DATABASE_URL" for
# databases started with Docker using `npm run db:start`.
# Note: If using with Prisma adapter, you need to use a `.env`
# file rather than a `.env.local` file to configure env vars.
# Postgres: DATABASE_URL=postgres://nextauth:password@127.0.0.1:5432/nextauth?synchronize=true

View File

@@ -1,29 +1,17 @@
import Link from "next/link"
import styles from "./footer.module.css"
import packageJSON from "package.json"
import Link from 'next/link'
import styles from './footer.module.css'
import { version } from 'package.json'
export default function Footer() {
export default function Footer () {
return (
<footer className={styles.footer}>
<hr />
<ul className={styles.navItems}>
<li className={styles.navItem}>
<a href="https://next-auth.js.org">Documentation</a>
</li>
<li className={styles.navItem}>
<a href="https://www.npmjs.com/package/next-auth">NPM</a>
</li>
<li className={styles.navItem}>
<a href="https://github.com/nextauthjs/next-auth-example">GitHub</a>
</li>
<li className={styles.navItem}>
<Link href="/policy">
<a>Policy</a>
</Link>
</li>
<li className={styles.navItem}>
<em>{packageJSON.version}</em>
</li>
<li className={styles.navItem}><a href='https://next-auth.js.org'>Documentation</a></li>
<li className={styles.navItem}><a href='https://www.npmjs.com/package/next-auth'>NPM</a></li>
<li className={styles.navItem}><a href='https://github.com/nextauthjs/next-auth-example'>GitHub</a></li>
<li className={styles.navItem}><Link href='/policy'><a>Policy</a></Link></li>
<li className={styles.navItem}><em>{version}</em></li>
</ul>
</footer>
)

View File

@@ -14,7 +14,7 @@
},
"license": "ISC",
"dependencies": {
"next": "^11.0.1",
"next": "^10.1.3",
"react": "^17.0.2",
"react-dom": "^17.0.2"
},

View File

@@ -1,5 +1,5 @@
// This is an example of how to read a JSON Web Token from an API route
import jwt from 'next-auth/jwt'
import jwt from "next-auth/jwt"
const secret = process.env.SECRET

View File

@@ -1,11 +1,8 @@
/** @type {import('@jest/types').Config.InitialOptions} */
module.exports = {
transform: {
"\\.js$": ["babel-jest", { configFile: "./config/babel.config.js" }],
},
rootDir: "../src",
setupFilesAfterEnv: ["../config/jest-setup.js"],
collectCoverageFrom: ["!client/__tests__/**"],
roots: ["../src"],
setupFilesAfterEnv: ["./jest-setup.js"],
testMatch: ["**/*.test.js"],
coverageDirectory: "../coverage",
}

View File

@@ -1,17 +0,0 @@
const fs = require("fs-extra")
const path = require("path")
try {
const packageJSONPath = path.join(process.cwd(), "package.json")
const packageJSON = JSON.parse(fs.readFileSync(packageJSONPath, "utf8"))
const sha8 = process.env.GITHUB_SHA.substr(0, 8)
const prNumber = process.env.PR_NUMBER
packageJSON.version = `0.0.0-pr.${prNumber}.${sha8}`
fs.writeFileSync(packageJSONPath, JSON.stringify(packageJSON))
} catch (error) {
console.error("Could not set PR version", error)
process.exit(1)
}

21107
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "next-auth",
"version": "3.29.9",
"version": "0.0.0-semantically-released",
"description": "Authentication for Next.js",
"homepage": "https://next-auth.js.org",
"repository": "https://github.com/nextauthjs/next-auth.git",
@@ -29,10 +29,11 @@
"./errors": "./dist/lib/errors.js"
},
"scripts": {
"postinstall": "npx husky install",
"build": "npm run build:js && npm run build:css",
"build:js": "node ./config/build.js && babel --config-file ./config/babel.config.js src --out-dir dist",
"build:css": "postcss --config config/postcss.config.js src/**/*.css --base src --dir dist && node config/wrap-css.js",
"dev:setup": "npm i && npm run build:css && cd app && npm i",
"dev:setup": "npm run build:css && cd app && npm i",
"dev": "cd app && npm run dev",
"watch": "npm run watch:js | npm run watch:css",
"watch:js": "babel --config-file ./config/babel.config.js --watch src --out-dir dist",
@@ -42,8 +43,7 @@
"test:types": "dtslint types --onlyTestTsNext",
"prepublishOnly": "npm run build",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"version:pr": "node ./config/version-pr"
"lint:fix": "eslint . --fix"
},
"files": [
"dist",
@@ -64,28 +64,25 @@
"license": "ISC",
"dependencies": {
"@babel/runtime": "^7.14.0",
"@next-auth/prisma-legacy-adapter": "0.1.2",
"@next-auth/typeorm-legacy-adapter": "0.1.4",
"futoin-hkdf": "^1.3.2",
"jose": "^1.27.2",
"jsonwebtoken": "^8.5.1",
"nodemailer": "^6.4.16",
"oauth": "^0.9.15",
"pkce-challenge": "^2.1.0",
"preact": "^10.4.1",
"preact-render-to-string": "^5.1.14",
"querystring": "^0.2.0"
"preact-render-to-string": "^5.1.14"
},
"peerDependencies": {
"react": "^16.13.1 || ^17",
"react-dom": "^16.13.1 || ^17"
},
"peerOptionalDependencies": {
"mongodb": "^3.5.9",
"mysql": "^2.18.1",
"mssql": "^6.2.1",
"pg": "^8.2.1",
"@prisma/client": "^2.16.1"
"nodemailer": "^6.4.16"
},
"peerDependenciesMeta": {
"nodemailer": {
"optional": true
}
},
"devDependencies": {
"@babel/cli": "^7.8.4",
@@ -94,10 +91,13 @@
"@babel/plugin-transform-runtime": "^7.13.15",
"@babel/preset-env": "^7.9.6",
"@babel/preset-react": "^7.13.13",
"@semantic-release/commit-analyzer": "^8.0.1",
"@semantic-release/github": "^7.2.0",
"@semantic-release/npm": "7.0.8",
"@semantic-release/release-notes-generator": "^9.0.1",
"@testing-library/jest-dom": "^5.12.0",
"@testing-library/react": "^11.2.6",
"@testing-library/user-event": "^13.1.9",
"@types/nodemailer": "^6.4.2",
"@types/react": "^17.0.0",
"@typescript-eslint/eslint-plugin": "^4.22.0",
"@typescript-eslint/parser": "^4.22.0",
@@ -119,13 +119,13 @@
"husky": "^6.0.0",
"jest": "^26.6.3",
"msw": "^0.28.2",
"next": "^11.0.1",
"next": "^10.0.5",
"postcss-cli": "^7.1.1",
"postcss-nested": "^4.2.1",
"prettier": "^2.2.1",
"pretty-quick": "^3.1.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"typescript": "^4.1.3",
"whatwg-fetch": "^3.6.2"
},

View File

@@ -1,64 +0,0 @@
import { useState } from "react"
import { rest } from "msw"
import { render, screen, waitFor } from "@testing-library/react"
import { server, mockSession } from "./helpers/mocks"
import { Provider, useSession } from ".."
import userEvent from "@testing-library/user-event"
beforeAll(() => {
server.listen()
})
afterEach(() => {
jest.clearAllMocks()
server.resetHandlers()
})
afterAll(() => {
server.close()
})
test("fetches the session once and re-uses it for different consumers", async () => {
const sessionRouteCall = jest.fn()
server.use(
rest.get("/api/auth/session", (req, res, ctx) => {
sessionRouteCall()
res(ctx.status(200), ctx.json(mockSession))
})
)
render(<ProviderFlow />)
await waitFor(() => {
expect(sessionRouteCall).toHaveBeenCalledTimes(1)
const session1 = screen.getByTestId("session-consumer-1").textContent
const session2 = screen.getByTestId("session-consumer-2").textContent
expect(session1).toEqual(session2)
})
})
function ProviderFlow({ options = {} }) {
return (
<>
<Provider options={options}>
<SessionConsumer />
<SessionConsumer testId="2" />
</Provider>
</>
)
}
function SessionConsumer({ testId = 1 }) {
const [session, loading] = useSession()
if (loading) return <span>loading</span>
return (
<div data-testid={`session-consumer-${testId}`}>
{JSON.stringify(session)}
</div>
)
}

View File

@@ -1,105 +0,0 @@
import { useState } from "react"
import userEvent from "@testing-library/user-event"
import { render, screen, waitFor } from "@testing-library/react"
import { server, mockCSRFToken } from "./helpers/mocks"
import logger from "../../lib/logger"
import { getCsrfToken } from ".."
import { rest } from "msw"
jest.mock("../../lib/logger", () => ({
__esModule: true,
default: {
warn: jest.fn(),
debug: jest.fn(),
error: jest.fn(),
},
proxyLogger(logger) {
return logger
},
}))
beforeAll(() => {
server.listen()
})
afterEach(() => {
server.resetHandlers()
jest.clearAllMocks()
})
afterAll(() => {
server.close()
})
test("returns the Cross Site Request Forgery Token (CSRF Token) required to make POST requests", async () => {
render(<CSRFFlow />)
userEvent.click(screen.getByRole("button"))
await waitFor(() => {
expect(screen.getByTestId("csrf-result").textContent).toEqual(
mockCSRFToken.csrfToken
)
})
})
test("when there's no CSRF token returned, it'll reflect that", async () => {
server.use(
rest.get("/api/auth/csrf", (req, res, ctx) =>
res(
ctx.status(200),
ctx.json({
...mockCSRFToken,
csrfToken: null,
})
)
)
)
render(<CSRFFlow />)
userEvent.click(screen.getByRole("button"))
await waitFor(() => {
expect(screen.getByTestId("csrf-result").textContent).toBe("null-response")
})
})
test("when the fetch fails it'll throw a client fetch error", async () => {
server.use(
rest.get("/api/auth/csrf", (req, res, ctx) =>
res(ctx.status(500), ctx.text("some error happened"))
)
)
render(<CSRFFlow />)
userEvent.click(screen.getByRole("button"))
await waitFor(() => {
expect(logger.error).toHaveBeenCalledTimes(1)
expect(logger.error).toBeCalledWith(
"CLIENT_FETCH_ERROR",
"csrf",
new SyntaxError("Unexpected token s in JSON at position 0")
)
})
})
function CSRFFlow() {
const [response, setResponse] = useState()
async function handleCSRF() {
const result = await getCsrfToken()
setResponse(result)
}
return (
<>
<p data-testid="csrf-result">
{response === null ? "null-response" : response || "no response"}
</p>
<button onClick={handleCSRF}>Get CSRF</button>
</>
)
}

View File

@@ -3,7 +3,6 @@ import { rest } from "msw"
import { randomBytes } from "crypto"
export const mockSession = {
ok: true,
user: {
image: null,
name: "John",
@@ -13,7 +12,6 @@ export const mockSession = {
}
export const mockProviders = {
ok: true,
github: {
id: "github",
name: "Github",
@@ -36,7 +34,6 @@ export const mockProviders = {
}
export const mockCSRFToken = {
ok: true,
csrfToken: randomBytes(32).toString("hex"),
}

View File

@@ -1,85 +0,0 @@
import { useState } from "react"
import userEvent from "@testing-library/user-event"
import { render, screen, waitFor } from "@testing-library/react"
import { server, mockProviders } from "./helpers/mocks"
import { getProviders } from ".."
import logger from "../../lib/logger"
import { rest } from "msw"
jest.mock("../../lib/logger", () => ({
__esModule: true,
default: {
warn: jest.fn(),
debug: jest.fn(),
error: jest.fn(),
},
proxyLogger(logger) {
return logger
},
}))
beforeAll(() => {
server.listen()
})
afterEach(() => {
server.resetHandlers()
jest.clearAllMocks()
})
afterAll(() => {
server.close()
})
test("when called it'll return the currently configured providers for sign in", async () => {
render(<ProvidersFlow />)
userEvent.click(screen.getByRole("button"))
await waitFor(() => {
expect(screen.getByTestId("providers-result").textContent).toEqual(
JSON.stringify(mockProviders)
)
})
})
test("when failing to fetch the providers, it'll log the error", async () => {
server.use(
rest.get("/api/auth/providers", (req, res, ctx) =>
res(ctx.status(500), ctx.text("some error happened"))
)
)
render(<ProvidersFlow />)
userEvent.click(screen.getByRole("button"))
await waitFor(() => {
expect(logger.error).toHaveBeenCalledTimes(1)
expect(logger.error).toBeCalledWith(
"CLIENT_FETCH_ERROR",
"providers",
new SyntaxError("Unexpected token s in JSON at position 0")
)
})
})
function ProvidersFlow() {
const [response, setResponse] = useState()
async function handleGerProviders() {
const result = await getProviders()
setResponse(result)
}
return (
<>
<p data-testid="providers-result">
{response === null
? "null-response"
: JSON.stringify(response) || "no response"}
</p>
<button onClick={handleGerProviders}>Get Providers</button>
</>
)
}

View File

@@ -1,10 +1,10 @@
import { render, screen, waitFor } from "@testing-library/react"
import { rest } from "msw"
import { server, mockSession } from "./helpers/mocks"
import { server, mockSession } from "./mocks"
import logger from "../../lib/logger"
import { useState, useEffect } from "react"
import { getSession } from ".."
import { getBroadcastEvents } from "./helpers/utils"
import { getBroadcastEvents } from "./utils"
jest.mock("../../lib/logger", () => ({
__esModule: true,
@@ -27,12 +27,10 @@ beforeEach(() => {
afterEach(() => {
server.resetHandlers()
jest.clearAllMocks()
jest.restoreAllMocks()
})
afterAll(() => {
server.close()
})
afterAll(() => server.close())
test("if it can fetch the session, it should store it in `localStorage`", async () => {
render(<SessionFlow />)
@@ -83,7 +81,7 @@ function SessionFlow() {
useEffect(() => {
async function fetchUserSession() {
try {
const result = await getSession()
const result = await getSession({})
setSession(result)
} catch (e) {
console.error(e)
@@ -92,7 +90,8 @@ function SessionFlow() {
fetchUserSession()
}, [])
if (session) return <pre>{JSON.stringify(session, null, 2)}</pre>
if (session) {
return <pre>{JSON.stringify(session, null, 2)}</pre>
}
return <p>No session</p>
}

View File

@@ -7,7 +7,7 @@ import {
mockCredentialsResponse,
mockEmailResponse,
mockGithubResponse,
} from "./helpers/mocks"
} from "./mocks"
import { signIn } from ".."
import { rest } from "msw"
@@ -36,7 +36,7 @@ beforeAll(() => {
})
beforeEach(() => {
jest.clearAllMocks()
jest.resetAllMocks()
server.resetHandlers()
})
@@ -284,7 +284,7 @@ function SignInFlow({
<p data-testid="signin-result">
{response ? JSON.stringify(response) : "no response"}
</p>
<button onClick={handleSignIn}>Sign in</button>
<button onClick={() => handleSignIn()}>Sign in</button>
</>
)
}

View File

@@ -1,10 +1,10 @@
import { useState } from "react"
import userEvent from "@testing-library/user-event"
import { render, screen, waitFor } from "@testing-library/react"
import { server, mockSignOutResponse } from "./helpers/mocks"
import { server, mockSignOutResponse } from "./mocks"
import { signOut } from ".."
import { rest } from "msw"
import { getBroadcastEvents } from "./helpers/utils"
import { getBroadcastEvents } from "./utils"
const { location } = window
@@ -24,7 +24,7 @@ beforeEach(() => {
})
afterEach(() => {
jest.clearAllMocks()
jest.resetAllMocks()
server.resetHandlers()
})
@@ -113,7 +113,7 @@ test("will broadcast the signout event to other tabs", async () => {
function SignOutFlow({ callbackUrl, redirect = true }) {
const [response, setResponse] = useState(null)
async function handleSignOut() {
async function setSignOutRes() {
const result = await signOut({ callbackUrl, redirect })
setResponse(result)
}
@@ -123,7 +123,7 @@ function SignOutFlow({ callbackUrl, redirect = true }) {
<p data-testid="signout-result">
{response ? JSON.stringify(response) : "no response"}
</p>
<button onClick={handleSignOut}>Sign out</button>
<button onClick={() => setSignOutRes()}>Sign out</button>
</>
)
}

103
src/client/index.d.ts vendored Normal file
View File

@@ -0,0 +1,103 @@
import * as React from 'react'
import { GetServerSidePropsContext } from 'next'
interface DefaultSession {
user: {
name: string | null
email: string | null
image: string | null
}
expires: Date | string
}
interface BroadcastMessage {
event?: 'session'
data?: {
trigger?: 'signout' | 'getSession'
}
clientId: string
timestamp: number
}
type GetSession<S extends Record<string, unknown> = DefaultSession> = (options: {
ctx?: GetServerSidePropsContext
req?: GetServerSidePropsContext['req']
event?: 'storage' | 'timer' | 'hidden' | string
triggerEvent?: boolean
}) => Promise<S>
export interface NextAuthConfig {
baseUrl: string
basePath: string
baseUrlServer: string
basePathServer: string
/** 0 means disabled (don't send); 60 means send every 60 seconds */
keepAlive: number
/** 0 means disabled (only use cache); 60 means sync if last checked > 60 seconds ago */
clientMaxAge: number
/** Used for timestamp since last sycned (in seconds) */
_clientLastSync: number
/** Stores timer for poll interval */
_clientSyncTimer: ReturnType<typeof setTimeout>
/** Tracks if event listeners have been added */
_eventListenersAdded: boolean
/** Stores last session response from hook */
_clientSession: DefaultSession | null | undefined
/** Used to store to function export by getSession() hook */
_getSession: any
}
export type GetCsrfToken = (
ctxOrReq: GetServerSidePropsContext & GetServerSidePropsContext['req']
) => Promise<string | null>
export interface SessionOptions {
baseUrl?: string
basePath?: string
clientMaxAge?: number
keepAlive?: number
}
export type Provider<S extends Record<string, unknown> = DefaultSession > = (options: {
children: React.ReactNode
session: S
options: SessionOptions
}) => React.ReactNode
export type SetOptions = (options: SessionOptions) => void
export type SessionContext = React.createContext<[DefaultSession | null, boolean]>
export type UseSession = () => [any, boolean]
export type GetProviders = () => Promise<any[]>
// Sign in types
export interface SignInOptions {
/** Defaults to the current URL. */
callbackUrl?: string
redirect?: boolean
}
export interface SignInResponse {
error: string | null
status: number
ok: boolean
url: string | null
}
export type SignIn<AuthorizationParams = Record<string, string>> = (
provider?: string,
options?: SignInOptions,
authorizationParams?: AuthorizationParams
) => SignInResponse
// Sign out types
interface SignOutResponse<RedirectType extends boolean=true> {
/** Defaults to the current URL. */
callbackUrl?: string
redirect?: RedirectType
}
export type SignOut<RedirectType extends boolean = true> = (params: SignOutResponse<RedirectType>) => RedirectType extends true ? Promise<{url?: string} | undefined> : undefined

View File

@@ -1,24 +0,0 @@
export default function Coinbase(options) {
return {
id: "coinbase",
name: "Coinbase",
type: "oauth",
version: "2.0",
scope: "wallet:user:email wallet:user:read",
params: { grant_type: "authorization_code" },
accessTokenUrl: "https://api.coinbase.com/oauth/token",
requestTokenUrl: "https://api.coinbase.com/oauth/token",
authorizationUrl:
"https://www.coinbase.com/oauth/authorize?response_type=code",
profileUrl: "https://api.coinbase.com/v2/user",
profile(profile) {
return {
id: profile.data.id,
name: profile.data.name,
email: profile.data.email,
image: profile.data.avatar_url,
}
},
...options,
}
}

View File

@@ -29,26 +29,26 @@
*/
export default function Dropbox(options) {
return {
id: 'dropbox',
name: 'Dropbox',
type: 'oauth',
version: '2.0',
scope: 'account_info.read',
params: { grant_type: 'authorization_code' },
accessTokenUrl: 'https://api.dropboxapi.com/oauth2/token',
id: "dropbox",
name: "Dropbox",
type: "oauth",
version: "2.0",
scope: "account_info.read",
params: { grant_type: "authorization_code" },
accessTokenUrl: "https://api.dropboxapi.com/oauth2/token",
authorizationUrl:
'https://www.dropbox.com/oauth2/authorize?token_access_type=offline&response_type=code',
profileUrl: 'https://api.dropboxapi.com/2/users/get_current_account',
"https://www.dropbox.com/oauth2/authorize?token_access_type=offline&response_type=code",
profileUrl: "https://api.dropboxapi.com/2/users/get_current_account",
profile: (profile) => {
return {
id: profile.account_id,
name: profile.name.display_name,
email: profile.email,
image: profile.profile_photo_url,
email_verified: profile.email_verified
email_verified: profile.email_verified,
}
},
protection: ["state", "pkce"],
...options
...options,
}
}

View File

@@ -1,5 +1,5 @@
import nodemailer from "nodemailer"
import logger from "../lib/logger"
import logger from '../lib/logger'
import requireOptional from '@balazsorban/require-optional'
export default function Email(options) {
return {
@@ -22,42 +22,34 @@ export default function Email(options) {
}
}
const sendVerificationRequest = ({
identifier: email,
url,
baseUrl,
provider,
}) => {
return new Promise((resolve, reject) => {
const { server, from } = provider
// Strip protocol from URL and use domain as site name
const site = baseUrl.replace(/^https?:\/\//, "")
nodemailer.createTransport(server).sendMail(
{
async function sendVerificationRequest ({ identifier: email, url, baseUrl, provider }) {
const { server, from } = provider
// Strip protocol from URL and use domain as site name
const site = baseUrl.replace(/^https?:\/\//, '')
try {
const nodemailer = requireOptional('nodemailer')
await nodemailer
.createTransport(server)
.sendMail({
to: email,
from,
subject: `Sign in to ${site}`,
text: text({ url, site, email }),
html: html({ url, site, email }),
},
(error) => {
if (error) {
logger.error("SEND_VERIFICATION_EMAIL_ERROR", error)
return reject(new Error("SEND_VERIFICATION_EMAIL_ERROR", error))
}
return resolve()
}
)
})
html: html({ url, site, email })
})
} catch (error) {
logger.error('SEND_VERIFICATION_EMAIL_ERROR', email, error)
throw new Error('SEND_VERIFICATION_EMAIL_ERROR')
}
}
// Email HTML body
const html = ({ url, site }) => {
// Insert invisible space into domains to prevent the
// the domain from being turned into a hyperlink by email
const html = ({ url, site, email }) => {
// Insert invisible space into domains and email address to prevent both the
// email address and the domain from being turned into a hyperlink by email
// clients like Outlook and Apple mail, as this is confusing because it seems
// like they are supposed to click it to sign in.
// like they are supposed to click on their email address to sign in.
const escapedEmail = `${email.replace(/\./g, "&#8203;.")}`
const escapedSite = `${site.replace(/\./g, "&#8203;.")}`
// Some simple styling options
@@ -72,12 +64,17 @@ const html = ({ url, site }) => {
<body style="background: ${backgroundColor};">
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center" style="padding: 10px 0px; font-size: 22px; font-family: Helvetica, Arial, sans-serif; color: ${textColor};">
Sign in to <strong>${escapedSite}</strong>
<td align="center" style="padding: 10px 0px 20px 0px; font-size: 22px; font-family: Helvetica, Arial, sans-serif; color: ${textColor};">
<strong>${escapedSite}</strong>
</td>
</tr>
</table>
<table width="100%" border="0" cellspacing="20" cellpadding="0" style="background: ${mainBackgroundColor}; max-width: 600px; margin: auto; border-radius: 10px;">
<tr>
<td align="center" style="padding: 10px 0px 0px 0px; font-size: 18px; font-family: Helvetica, Arial, sans-serif; color: ${textColor};">
Sign in as <strong>${escapedEmail}</strong>
</td>
</tr>
<tr>
<td align="center" style="padding: 20px 0;">
<table border="0" cellspacing="0" cellpadding="0">

View File

@@ -1,20 +0,0 @@
export default function Freshbooks(options) {
return {
id: 'freshbooks',
name: 'Freshbooks',
type: 'oauth',
version: '2.0',
params: { grant_type: 'authorization_code' },
accessTokenUrl: 'https://api.freshbooks.com/auth/oauth/token',
authorizationUrl: 'https://auth.freshbooks.com/service/auth/oauth/authorize?response_type=code',
profileUrl: 'https://api.freshbooks.com/auth/api/v1/users/me',
async profile(profile) {
return {
id: profile.response.id,
name: `${profile.response.first_name} ${profile.response.last_name}`,
email: profile.response.email,
};
},
...options
};
}

View File

@@ -1,18 +0,0 @@
export default function Naver(options) {
return {
id: "naver",
name: "Naver",
type: "oauth",
version: "2.0",
params: { grant_type: "authorization_code" },
protection: ["state"],
accessTokenUrl: "https://nid.naver.com/oauth2.0/token",
authorizationUrl:
"https://nid.naver.com/oauth2.0/authorize?response_type=code",
profileUrl: "https://openapi.naver.com/v1/nid/me",
profile(profile) {
return profile.response
},
...options,
}
}

View File

@@ -1,19 +0,0 @@
export default function OneLogin(options) {
return {
id: "onelogin",
name: "OneLogin",
type: "oauth",
version: "2.0",
scope: "openid profile name email",
params: { grant_type: "authorization_code" },
// These will be different depending on the Org.
accessTokenUrl: `https://${options.domain}/oidc/2/token`,
requestTokenUrl: `https://${options.domain}/oidc/2/auth`,
authorizationUrl: `https://${options.domain}/oidc/2/auth?response_type=code`,
profileUrl: `https://${options.domain}/oidc/2/me`,
profile(profile) {
return { ...profile, id: profile.sub }
},
...options,
}
}

View File

@@ -15,7 +15,10 @@ export default function Twitter(options) {
id: profile.id_str,
name: profile.name,
email: profile.email,
image: profile.profile_image_url_https.replace(/_normal\.(jpg|png|gif)$/, ".$1"),
image: profile.profile_image_url_https.replace(
/_normal\.(jpg|png|gif)$/,
".$1"
),
}
},
...options,

View File

@@ -1,16 +1,16 @@
export default function WorkOS(options) {
const domain = options.domain || 'api.workos.com';
const domain = options.domain || "api.workos.com"
return {
id: 'workos',
name: 'WorkOS',
type: 'oauth',
version: '2.0',
scope: '',
id: "workos",
name: "WorkOS",
type: "oauth",
version: "2.0",
scope: "",
params: {
grant_type: 'authorization_code',
grant_type: "authorization_code",
client_id: options.clientId,
client_secret: options.clientSecret
client_secret: options.clientSecret,
},
accessTokenUrl: `https://${domain}/sso/token`,
authorizationUrl: `https://${domain}/sso/authorize?response_type=code`,
@@ -18,9 +18,9 @@ export default function WorkOS(options) {
profile: (profile) => {
return {
...profile,
name: `${profile.first_name} ${profile.last_name}`
name: `${profile.first_name} ${profile.last_name}`,
}
},
...options
...options,
}
}

View File

@@ -4,7 +4,7 @@ export default function Yandex(options) {
name: "Yandex",
type: "oauth",
version: "2.0",
scope: "login:email login:info login:avatar",
scope: "login:email login:info",
params: { grant_type: "authorization_code" },
accessTokenUrl: "https://oauth.yandex.ru/token",
requestTokenUrl: "https://oauth.yandex.ru/token",
@@ -15,7 +15,7 @@ export default function Yandex(options) {
id: profile.id,
name: profile.real_name,
email: profile.default_email,
image: profile.is_avatar_empty ? null : `https://avatars.yandex.net/get-yapic/${profile.default_avatar_id}/islands-200`,
image: null,
}
},
...options,

View File

@@ -1,4 +1,3 @@
import adapters from "../adapters"
import jwt from "../lib/jwt"
import parseUrl from "../lib/parse-url"
import logger, { setLogger } from "../lib/logger"
@@ -15,22 +14,14 @@ import csrfTokenHandler from "./lib/csrf-token-handler"
import * as pkce from "./lib/oauth/pkce-handler"
import * as state from "./lib/oauth/state-handler"
import optionalRequire from "@balazsorban/require-optional"
// To work properly in production with OAuth providers the NEXTAUTH_URL
// environment variable must be set.
if (!process.env.NEXTAUTH_URL) {
logger.warn("NEXTAUTH_URL", "NEXTAUTH_URL environment variable not set")
}
function isValidHttpUrl(url, baseUrl) {
try {
return /^https?:/.test(
new URL(url, url.startsWith("/") ? baseUrl : undefined).protocol
)
} catch {
return false
}
}
/**
* @param {import("next").NextApiRequest} req
* @param {import("next").NextApiResponse} res
@@ -81,23 +72,6 @@ async function NextAuthHandler(req, res, userOptions) {
...userOptions.cookies,
}
const errorPage = userOptions.pages?.error ?? `${baseUrl}${basePath}/error`
const callbackUrlParam = req.query?.callbackUrl
if (callbackUrlParam && !isValidHttpUrl(callbackUrlParam, baseUrl)) {
return res.redirect(`${errorPage}?error=Configuration`)
}
const { callbackUrl: defaultCallbackUrl } = cookie.defaultCookies(
userOptions.useSecureCookies ?? baseUrl.startsWith("https://")
)
const callbackUrlCookie =
req.cookies?.[cookies?.callbackUrl?.name ?? defaultCallbackUrl.name]
if (callbackUrlCookie && !isValidHttpUrl(callbackUrlCookie, baseUrl)) {
return res.redirect(`${errorPage}?error=Configuration`)
}
const secret = createSecret({ userOptions, basePath, baseUrl })
const providers = parseProviders({
@@ -129,13 +103,6 @@ async function NextAuthHandler(req, res, userOptions) {
const maxAge = 30 * 24 * 60 * 60 // Sessions expire after 30 days of being idle
// Parse database / adapter
// If adapter is provided, use it (advanced usage, overrides database)
// If database URI or config object is provided, use it (simple usage)
const adapter =
userOptions.adapter ??
(userOptions.database && adapters.Default(userOptions.database))
// User provided options are overriden by other options,
// except for the options with special handling above
req.options = {
@@ -156,7 +123,7 @@ async function NextAuthHandler(req, res, userOptions) {
providers,
// Session options
session: {
jwt: !adapter, // If no adapter specified, force use of JSON Web Tokens (stateless)
jwt: !userOptions.adapter, // If no adapter specified, force use of JSON Web Tokens (stateless)
maxAge,
updateAge: 24 * 60 * 60, // Sessions updated only if session is greater than this value (0 = always, 24*60*60 = every 24 hours)
...userOptions.session,
@@ -307,9 +274,7 @@ async function NextAuthHandler(req, res, userOptions) {
}
return res
.status(400)
.end(
`Error: This action with HTTP ${req.method} is not supported by NextAuth.js`
)
.end(`Error: HTTP ${req.method} is not supported for ${req.url}`)
})
}

View File

@@ -8,115 +8,115 @@
* As only partial functionlity is required, only the code we need has been incorporated here
* (with fixes for specific issues) to keep dependancy size down.
*/
export function set(res, name, value, options = {}) {
export function set (res, name, value, options = {}) {
const stringValue =
typeof value === "object" ? "j:" + JSON.stringify(value) : String(value)
typeof value === 'object' ? 'j:' + JSON.stringify(value) : String(value)
if ("maxAge" in options) {
if ('maxAge' in options) {
options.expires = new Date(Date.now() + options.maxAge)
options.maxAge /= 1000
}
// Preserve any existing cookies that have already been set in the same session
let setCookieHeader = res.getHeader("Set-Cookie") || []
let setCookieHeader = res.getHeader('Set-Cookie') || []
// If not an array (i.e. a string with a single cookie) convert it into an array
if (!Array.isArray(setCookieHeader)) {
setCookieHeader = [setCookieHeader]
}
setCookieHeader.push(_serialize(name, String(stringValue), options))
res.setHeader("Set-Cookie", setCookieHeader)
res.setHeader('Set-Cookie', setCookieHeader)
}
function _serialize(name, val, options) {
function _serialize (name, val, options) {
const fieldContentRegExp = /^[\u0009\u0020-\u007e\u0080-\u00ff]+$/ // eslint-disable-line no-control-regex
const opt = options || {}
const enc = opt.encode || encodeURIComponent
if (typeof enc !== "function") {
throw new TypeError("option encode is invalid")
if (typeof enc !== 'function') {
throw new TypeError('option encode is invalid')
}
if (!fieldContentRegExp.test(name)) {
throw new TypeError("argument name is invalid")
throw new TypeError('argument name is invalid')
}
const value = enc(val)
if (value && !fieldContentRegExp.test(value)) {
throw new TypeError("argument val is invalid")
throw new TypeError('argument val is invalid')
}
let str = name + "=" + value
let str = name + '=' + value
if (opt.maxAge != null) {
const maxAge = opt.maxAge - 0
if (isNaN(maxAge) || !isFinite(maxAge)) {
throw new TypeError("option maxAge is invalid")
throw new TypeError('option maxAge is invalid')
}
str += "; Max-Age=" + Math.floor(maxAge)
str += '; Max-Age=' + Math.floor(maxAge)
}
if (opt.domain) {
if (!fieldContentRegExp.test(opt.domain)) {
throw new TypeError("option domain is invalid")
throw new TypeError('option domain is invalid')
}
str += "; Domain=" + opt.domain
str += '; Domain=' + opt.domain
}
if (opt.path) {
if (!fieldContentRegExp.test(opt.path)) {
throw new TypeError("option path is invalid")
throw new TypeError('option path is invalid')
}
str += "; Path=" + opt.path
str += '; Path=' + opt.path
} else {
str += "; Path=/"
str += '; Path=/'
}
if (opt.expires) {
let expires = opt.expires
if (typeof opt.expires.toUTCString === "function") {
if (typeof opt.expires.toUTCString === 'function') {
expires = opt.expires.toUTCString()
} else {
const dateExpires = new Date(opt.expires)
expires = dateExpires.toUTCString()
}
str += "; Expires=" + expires
str += '; Expires=' + expires
}
if (opt.httpOnly) {
str += "; HttpOnly"
str += '; HttpOnly'
}
if (opt.secure) {
str += "; Secure"
str += '; Secure'
}
if (opt.sameSite) {
const sameSite =
typeof opt.sameSite === "string"
typeof opt.sameSite === 'string'
? opt.sameSite.toLowerCase()
: opt.sameSite
switch (sameSite) {
case true:
str += "; SameSite=Strict"
str += '; SameSite=Strict'
break
case "lax":
str += "; SameSite=Lax"
case 'lax':
str += '; SameSite=Lax'
break
case "strict":
str += "; SameSite=Strict"
case 'strict':
str += '; SameSite=Strict'
break
case "none":
str += "; SameSite=None"
case 'none':
str += '; SameSite=None'
break
default:
throw new TypeError("option sameSite is invalid")
throw new TypeError('option sameSite is invalid')
}
}
@@ -134,47 +134,46 @@ function _serialize(name, val, options) {
* @TODO Review cookie settings (names, options)
* @return {import("types").CookiesOptions}
*/
export function defaultCookies(useSecureCookies) {
const cookiePrefix = useSecureCookies ? "__Secure-" : ""
export function defaultCookies (useSecureCookies) {
const cookiePrefix = useSecureCookies ? '__Secure-' : ''
return {
// default cookie options
sessionToken: {
name: `${cookiePrefix}next-auth.session-token`,
options: {
httpOnly: true,
sameSite: "lax",
path: "/",
secure: useSecureCookies,
},
sameSite: 'lax',
path: '/',
secure: useSecureCookies
}
},
callbackUrl: {
name: `${cookiePrefix}next-auth.callback-url`,
options: {
httpOnly: true,
sameSite: "lax",
path: "/",
secure: useSecureCookies,
},
sameSite: 'lax',
path: '/',
secure: useSecureCookies
}
},
csrfToken: {
// Default to __Host- for CSRF token for additional protection if using useSecureCookies
// NB: The `__Host-` prefix is stricter than the `__Secure-` prefix.
name: `${useSecureCookies ? "__Host-" : ""}next-auth.csrf-token`,
name: `${useSecureCookies ? '__Host-' : ''}next-auth.csrf-token`,
options: {
httpOnly: true,
sameSite: "lax",
path: "/",
secure: useSecureCookies,
},
sameSite: 'lax',
path: '/',
secure: useSecureCookies
}
},
pkceCodeVerifier: {
name: `${cookiePrefix}next-auth.pkce.code_verifier`,
options: {
httpOnly: true,
sameSite: "lax",
path: "/",
secure: useSecureCookies,
},
},
sameSite: 'lax',
path: '/',
secure: useSecureCookies
}
}
}
}

View File

@@ -3,7 +3,7 @@ import * as cookie from './cookie'
/**
* Ensure CSRF Token cookie is set for any subsequent requests.
* Used as part of the strategy for mitigation for CSRF tokens.
* Used as part of the strateigy for mitigation for CSRF tokens.
*
* Creates a cookie like 'next-auth.csrf-token' with the value 'token|hash',
* where 'token' is the CSRF token and 'hash' is a hash made of the token and

View File

@@ -15,7 +15,7 @@
* @return {Promise<boolean|never>} Return `true` (or a modified JWT) to allow sign in
* Return `false` to deny access
*/
export async function signIn() {
export async function signIn () {
return true
}
@@ -28,9 +28,10 @@ export async function signIn() {
* @param {string} baseUrl Default base URL of site (can be used as fallback)
* @return {Promise<string>} URL the client will be redirect to
*/
export async function redirect(url, baseUrl) {
if (url.startsWith("/")) return `${baseUrl}${url}`
else if (new URL(url).origin === baseUrl) return url
export async function redirect (url, baseUrl) {
if (url.startsWith(baseUrl)) {
return url
}
return baseUrl
}
@@ -42,7 +43,7 @@ export async function redirect(url, baseUrl) {
* @param {object} token JSON Web Token (if enabled)
* @return {Promise<object>} Session that will be returned to the client
*/
export async function session(session) {
export async function session (session) {
return session
}
@@ -58,6 +59,6 @@ export async function session(session) {
* @param {object} oAuthProfile OAuth profile - only available on sign in
* @return {Promise<object>} JSON Web Token that will be saved
*/
export async function jwt(token) {
export async function jwt (token) {
return token
}

View File

@@ -30,7 +30,6 @@ export default async function oAuthCallback(req) {
provider.id,
code
)
logger.debug("OAUTH_CALLBACK_HANDLER_ERROR", req.body)
throw error
}
}
@@ -63,7 +62,7 @@ export default async function oAuthCallback(req) {
return getProfile({ profileData, provider, tokens, user })
} catch (error) {
logger.error("OAUTH_GET_ACCESS_TOKEN_ERROR", error, provider.id)
logger.error("OAUTH_GET_ACCESS_TOKEN_ERROR", error, provider.id, code)
throw error
}
}
@@ -75,11 +74,7 @@ export default async function oAuthCallback(req) {
// eslint-disable-next-line camelcase
const { token_secret } = await client.getOAuthRequestToken(provider.params)
const tokens = await client.getOAuthAccessToken(
oauth_token,
token_secret,
oauth_verifier
)
const tokens = await client.getOAuthAccessToken(oauth_token, token_secret, oauth_verifier)
const profileData = await client.get(
provider.profileUrl,
tokens.oauth_token,
@@ -148,11 +143,11 @@ async function getProfile({ profileData, tokens, provider, user }) {
// If we didn't get a response either there was a problem with the provider
// response *or* the user cancelled the action with the provider.
//
// Unfortunately, we can't tell which - at least not in a way that works for
// Unfortuately, we can't tell which - at least not in a way that works for
// all providers, so we return an empty object; the user should then be
// redirected back to the sign up page. We log the error to help developers
// who might be trying to debug this when configuring a new provider.
logger.error("OAUTH_PARSE_PROFILE_ERROR", exception)
logger.error("OAUTH_PARSE_PROFILE_ERROR", exception, profileData)
return {
profile: null,
account: null,

View File

@@ -180,44 +180,51 @@ async function getOAuth2AccessToken(code, provider, codeVerifier) {
const postData = querystring.stringify(params)
return new Promise((resolve, reject) => {
this._request("POST", url, headers, postData, null, (error, data) => {
if (error) {
logger.error("OAUTH_GET_ACCESS_TOKEN_ERROR", error)
return reject(error)
}
let raw
try {
// As of http://tools.ietf.org/html/draft-ietf-oauth-v2-07
// responses should be in JSON
raw = JSON.parse(data)
} catch {
// However both Facebook + Github currently use rev05 of the spec and neither
// seem to specify a content-type correctly in their response headers. :(
// Clients of these services suffer a minor performance cost.
raw = querystring.parse(data)
}
let accessToken
if (provider.id === "slack") {
const { ok, error } = raw
if (!ok) {
this._request(
"POST",
url,
headers,
postData,
null,
(error, data, response) => {
if (error) {
logger.error("OAUTH_GET_ACCESS_TOKEN_ERROR", error, data, response)
return reject(error)
}
accessToken = raw.authed_user.access_token
} else {
accessToken = raw.access_token
}
let raw
try {
// As of http://tools.ietf.org/html/draft-ietf-oauth-v2-07
// responses should be in JSON
raw = JSON.parse(data)
} catch {
// However both Facebook + Github currently use rev05 of the spec and neither
// seem to specify a content-type correctly in their response headers. :(
// Clients of these services suffer a minor performance cost.
raw = querystring.parse(data)
}
resolve({
accessToken,
accessTokenExpires: null,
refreshToken: raw.refresh_token,
idToken: raw.id_token,
...raw,
})
})
let accessToken
if (provider.id === "slack") {
const { ok, error } = raw
if (!ok) {
return reject(error)
}
accessToken = raw.authed_user.access_token
} else {
accessToken = raw.access_token
}
resolve({
accessToken,
accessTokenExpires: null,
refreshToken: raw.refresh_token,
idToken: raw.id_token,
...raw,
})
}
)
})
}

View File

@@ -36,11 +36,7 @@ export async function handleCallback (req, res) {
pkceLength: PKCE_LENGTH,
method: PKCE_CODE_CHALLENGE_METHOD
})
// remove PKCE after it has been used
cookie.set(res, cookies.pkceCodeVerifier.name, "", {
...cookies.pkceCodeVerifier.options,
maxAge: 0
})
cookie.set(res, cookies.pkceCodeVerifier.name, null, { maxAge: 0 }) // remove PKCE after it has been used
} catch (error) {
logger.error('CALLBACK_OAUTH_ERROR', error)
return res.redirect(`${baseUrl}${basePath}/error?error=OAuthCallback`)

View File

@@ -1,23 +1,23 @@
import oAuthClient from '../oauth/client'
import logger from '../../../lib/logger'
import oAuthClient from "../oauth/client"
import logger from "../../../lib/logger"
/** @param {import("types/internals").NextAuthRequest} req */
export default async function getAuthorizationUrl (req) {
export default async function getAuthorizationUrl(req) {
const { provider } = req.options
delete req.query?.nextauth
const params = {
...provider.authorizationParams,
...req.query
...req.query,
}
const client = oAuthClient(provider)
if (provider.version?.startsWith('2.')) {
if (provider.version?.startsWith("2.")) {
// Handle OAuth v2.x
let url = client.getAuthorizeUrl({
scope: provider.scope,
...params,
redirect_uri: provider.callbackUrl
redirect_uri: provider.callbackUrl,
})
// If the authorizationUrl specified in the config has query parameters on it
@@ -27,13 +27,13 @@ export default async function getAuthorizationUrl (req) {
// which inadvertantly strips them.
//
// https://github.com/ciaranj/node-oauth/pull/193
if (provider.authorizationUrl.includes('?')) {
if (provider.authorizationUrl.includes("?")) {
const parseUrl = new URL(provider.authorizationUrl)
const baseUrl = `${parseUrl.origin}${parseUrl.pathname}?`
url = url.replace(baseUrl, provider.authorizationUrl + '&')
url = url.replace(baseUrl, provider.authorizationUrl + "&")
}
logger.debug('GET_AUTHORIZATION_URL', url)
logger.debug("GET_AUTHORIZATION_URL", url)
return url
}
@@ -42,12 +42,12 @@ export default async function getAuthorizationUrl (req) {
const url = `${provider.authorizationUrl}?${new URLSearchParams({
oauth_token: tokens.oauth_token,
oauth_token_secret: tokens.oauth_token_secret,
...tokens.params
...tokens.params,
})}`
logger.debug('GET_AUTHORIZATION_URL', url)
logger.debug("GET_AUTHORIZATION_URL", url)
return url
} catch (error) {
logger.error('GET_AUTHORIZATION_URL_ERROR', error)
logger.error("GET_AUTHORIZATION_URL_ERROR", error)
throw error
}
}

View File

@@ -1,18 +1,12 @@
import { h } from "preact" // eslint-disable-line no-unused-vars
import { h } from 'preact' // eslint-disable-line no-unused-vars
export default function signin({
csrfToken,
providers,
callbackUrl,
email,
error: errorType,
}) {
export default function signin ({ csrfToken, providers, callbackUrl, email, error: errorType }) {
// We only want to render providers
const providersToRender = providers.filter((provider) => {
if (provider.type === "oauth" || provider.type === "email") {
const providersToRender = providers.filter(provider => {
if (provider.type === 'oauth' || provider.type === 'email') {
// Always render oauth and email type providers
return true
} else if (provider.type === "credentials" && provider.credentials) {
} else if (provider.type === 'credentials' && provider.credentials) {
// Only render credentials type provider if credentials are defined
return true
}
@@ -21,93 +15,70 @@ export default function signin({
})
const errors = {
Signin: "Try signing in with a different account.",
OAuthSignin: "Try signing in with a different account.",
OAuthCallback: "Try signing in with a different account.",
OAuthCreateAccount: "Try signing in with a different account.",
EmailCreateAccount: "Try signing in with a different account.",
Callback: "Try signing in with a different account.",
OAuthAccountNotLinked:
"To confirm your identity, sign in with the same account you used originally.",
EmailSignin: "Check your email inbox.",
CredentialsSignin:
"Sign in failed. Check the details you provided are correct.",
default: "Unable to sign in.",
Signin: 'Try signing with a different account.',
OAuthSignin: 'Try signing with a different account.',
OAuthCallback: 'Try signing with a different account.',
OAuthCreateAccount: 'Try signing with a different account.',
EmailCreateAccount: 'Try signing with a different account.',
Callback: 'Try signing with a different account.',
OAuthAccountNotLinked: 'To confirm your identity, sign in with the same account you used originally.',
EmailSignin: 'Check your email address.',
CredentialsSignin: 'Sign in failed. Check the details you provided are correct.',
default: 'Unable to sign in.'
}
const error = errorType && (errors[errorType] ?? errors.default)
return (
<div className="signin">
{error && (
<div className="error">
<div className='signin'>
{error &&
<div className='error'>
<p>{error}</p>
</div>
)}
{providersToRender.map((provider, i) => (
<div key={provider.id} className="provider">
{provider.type === "oauth" && (
<form action={provider.signinUrl} method="POST">
<input type="hidden" name="csrfToken" value={csrfToken} />
{callbackUrl && (
<input type="hidden" name="callbackUrl" value={callbackUrl} />
)}
<button type="submit" className="button">
Sign in with {provider.name}
</button>
</form>
)}
{(provider.type === "email" || provider.type === "credentials") &&
i > 0 &&
providersToRender[i - 1].type !== "email" &&
providersToRender[i - 1].type !== "credentials" && <hr />}
{provider.type === "email" && (
<form action={provider.signinUrl} method="POST">
<input type="hidden" name="csrfToken" value={csrfToken} />
<label for={`input-email-for-${provider.id}-provider`}>
Email
</label>
<input
id={`input-email-for-${provider.id}-provider`}
autoFocus
type="text"
name="email"
value={email}
placeholder="email@example.com"
/>
<button type="submit">Sign in with {provider.name}</button>
</form>
)}
{provider.type === "credentials" && (
<form action={provider.callbackUrl} method="POST">
<input type="hidden" name="csrfToken" value={csrfToken} />
{Object.keys(provider.credentials).map((credential) => {
</div>}
{providersToRender.map((provider, i) =>
<div key={provider.id} className='provider'>
{provider.type === 'oauth' &&
<form action={provider.signinUrl} method='POST'>
<input type='hidden' name='csrfToken' value={csrfToken} />
{callbackUrl && <input type='hidden' name='callbackUrl' value={callbackUrl} />}
<button type='submit' className='button'>Sign in with {provider.name}</button>
</form>}
{(provider.type === 'email' || provider.type === 'credentials') && (i > 0) &&
providersToRender[i - 1].type !== 'email' && providersToRender[i - 1].type !== 'credentials' &&
<hr />}
{provider.type === 'email' &&
<form action={provider.signinUrl} method='POST'>
<input type='hidden' name='csrfToken' value={csrfToken} />
<label for={`input-email-for-${provider.id}-provider`}>Email</label>
<input id={`input-email-for-${provider.id}-provider`} autoFocus type='text' name='email' value={email} placeholder='email@example.com' />
<button type='submit'>Sign in with {provider.name}</button>
</form>}
{provider.type === 'credentials' &&
<form action={provider.callbackUrl} method='POST'>
<input type='hidden' name='csrfToken' value={csrfToken} />
{Object.keys(provider.credentials).map(credential => {
return (
<div key={`input-group-${provider.id}`}>
<label
for={`input-${credential}-for-${provider.id}-provider`}
>
{provider.credentials[credential].label || credential}
>{provider.credentials[credential].label || credential}
</label>
<input
name={credential}
id={`input-${credential}-for-${provider.id}-provider`}
type={provider.credentials[credential].type || "text"}
value={provider.credentials[credential].value || ""}
placeholder={
provider.credentials[credential].placeholder || ""
}
type={provider.credentials[credential].type || 'text'}
value={provider.credentials[credential].value || ''}
placeholder={provider.credentials[credential].placeholder || ''}
/>
</div>
)
})}
<button type="submit">Sign in with {provider.name}</button>
</form>
)}
{(provider.type === "email" || provider.type === "credentials") &&
i + 1 < providersToRender.length && <hr />}
<button type='submit'>Sign in with {provider.name}</button>
</form>}
{(provider.type === 'email' || provider.type === 'credentials') && ((i + 1) < providersToRender.length) &&
<hr />}
</div>
))}
)}
</div>
)
}

View File

@@ -77,7 +77,7 @@ export default async function callback(req, res) {
account,
OAuthProfile
)
if (signInCallbackResponse === false) {
if (!signInCallbackResponse) {
return res.redirect(
`${baseUrl}${basePath}/error?error=AccessDenied`
)
@@ -85,16 +85,11 @@ export default async function callback(req, res) {
return res.redirect(signInCallbackResponse)
}
} catch (error) {
if (error instanceof Error) {
return res.redirect(
`${baseUrl}${basePath}/error?error=${encodeURIComponent(
error.message
)}`
)
}
// TODO: Remove in a future major release
logger.warn("SIGNIN_CALLBACK_REJECT_REDIRECT")
return res.redirect(error)
return res.redirect(
`${baseUrl}${basePath}/error?error=${encodeURIComponent(
error.message
)}`
)
}
// Sign user in
@@ -226,22 +221,17 @@ export default async function callback(req, res) {
account,
{ email }
)
if (signInCallbackResponse === false) {
if (!signInCallbackResponse) {
return res.redirect(`${baseUrl}${basePath}/error?error=AccessDenied`)
} else if (typeof signInCallbackResponse === "string") {
return res.redirect(signInCallbackResponse)
}
} catch (error) {
if (error instanceof Error) {
return res.redirect(
`${baseUrl}${basePath}/error?error=${encodeURIComponent(
error.message
)}`
)
}
// TODO: Remove in a future major release
logger.warn("SIGNIN_CALLBACK_REJECT_REDIRECT")
return res.redirect(error)
return res.redirect(
`${baseUrl}${basePath}/error?error=${encodeURIComponent(
error.message
)}`
)
}
// Sign user in
@@ -336,7 +326,8 @@ export default async function callback(req, res) {
let userObjectReturnedFromAuthorizeHandler
try {
userObjectReturnedFromAuthorizeHandler = await provider.authorize(
credentials, {...req, options: {}, cookies: {}}
credentials,
{ ...req, options: {}, cookies: {} }
)
if (!userObjectReturnedFromAuthorizeHandler) {
return res
@@ -346,16 +337,13 @@ export default async function callback(req, res) {
provider.id
)}`
)
} else if (typeof userObjectReturnedFromAuthorizeHandler === "string") {
return res.redirect(userObjectReturnedFromAuthorizeHandler)
}
} catch (error) {
if (error instanceof Error) {
return res.redirect(
`${baseUrl}${basePath}/error?error=${encodeURIComponent(
error.message
)}`
)
}
return res.redirect(error)
return res.redirect(
`${baseUrl}${basePath}/error?error=${encodeURIComponent(error.message)}`
)
}
const user = userObjectReturnedFromAuthorizeHandler
@@ -367,20 +355,17 @@ export default async function callback(req, res) {
account,
credentials
)
if (signInCallbackResponse === false) {
if (!signInCallbackResponse) {
return res
.status(403)
.redirect(`${baseUrl}${basePath}/error?error=AccessDenied`)
} else if (typeof signInCallbackResponse === "string") {
return res.redirect(signInCallbackResponse)
}
} catch (error) {
if (error instanceof Error) {
return res.redirect(
`${baseUrl}${basePath}/error?error=${encodeURIComponent(
error.message
)}`
)
}
return res.redirect(error)
return res.redirect(
`${baseUrl}${basePath}/error?error=${encodeURIComponent(error.message)}`
)
}
const defaultJwtPayload = {

View File

@@ -40,10 +40,6 @@ export default async function signin(req, res) {
// complains about this we can make strict RFC 2821 compliance an option.
const email = req.body.email?.toLowerCase() ?? null
if (!email) {
return res.redirect(`${baseUrl}${basePath}/error?error=EmailSignin`)
}
// If is an existing user return a user object (otherwise use placeholder)
const profile = (await getUserByEmail(email)) || { email }
const account = { id: provider.id, type: "email", providerAccountId: email }
@@ -60,14 +56,9 @@ export default async function signin(req, res) {
return res.redirect(signInCallbackResponse)
}
} catch (error) {
if (error instanceof Error) {
return res.redirect(
`${baseUrl}${basePath}/error?error=${encodeURIComponent(error)}`
)
}
// TODO: Remove in a future major release
logger.warn("SIGNIN_CALLBACK_REJECT_REDIRECT")
return res.redirect(error)
return res.redirect(
`${baseUrl}${basePath}/error?error=${encodeURIComponent(error)}`
)
}
try {

2
types/index.d.ts vendored
View File

@@ -82,7 +82,7 @@ export interface NextAuthOptions {
* signOut: '/auth/signout',
* error: '/auth/error',
* verifyRequest: '/auth/verify-request',
* newUser: '/auth/new-user'
* newUser: null
* }
* ```
*

31
types/providers.d.ts vendored
View File

@@ -1,6 +1,5 @@
import { Profile, TokenSet, User } from "."
import { Awaitable, NextApiRequest } from "./internals/utils"
import { Options as SMTPConnectionOptions } from 'nodemailer/lib/smtp-connection'
export type ProviderType = "oauth" | "email" | "credentials"
@@ -27,7 +26,7 @@ export interface OAuthConfig<P extends Record<string, unknown> = Profile>
headers?: Record<string, any>
type: "oauth"
version: string
scope: string | string[]
scope: string
params: { grant_type: string }
accessTokenUrl: string
requestTokenUrl?: string
@@ -64,7 +63,6 @@ export type OAuthProviderType =
| "Box"
| "Bungie"
| "Cognito"
| "Coinbase"
| "Discord"
| "Dropbox"
| "EVEOnline"
@@ -72,7 +70,6 @@ export type OAuthProviderType =
| "FACEIT"
| "FortyTwo"
| "Foursquare"
| "Freshbooks"
| "FusionAuth"
| "GitHub"
| "GitLab"
@@ -85,10 +82,8 @@ export type OAuthProviderType =
| "Mailchimp"
| "MailRu"
| "Medium"
| "Naver"
| "Netlify"
| "Okta"
| "OneLogin"
| "Osso"
| "Reddit"
| "Salesforce"
@@ -121,15 +116,29 @@ interface CredentialsConfig<C extends Record<string, CredentialInput> = {}>
extends CommonProviderOptions {
type: "credentials"
credentials: C
authorize(credentials: Record<keyof C, string>, req: NextApiRequest): Awaitable<User | null>
authorize(
credentials: Record<keyof C, string>,
req: NextApiRequest
): Awaitable<User | null>
}
export type CredentialsProvider = <C extends Record<string, CredentialInput>>(
options: Partial<CredentialsConfig<C>>
) => CredentialsConfig<C>
export type CredentialsProvider = (
options: Partial<CredentialsConfig>
) => CredentialsConfig
export type CredentialsProviderType = "Credentials"
/** Email Provider */
export interface EmailConfigServerOptions {
host: string
port: number
auth: {
user: string
pass: string
}
}
export type SendVerificationRequest = (params: {
identifier: string
url: string
@@ -141,7 +150,7 @@ export type SendVerificationRequest = (params: {
export interface EmailConfig extends CommonProviderOptions {
type: "email"
// TODO: Make use of https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html
server: string | SMTPConnectionOptions
server: string | EmailConfigServerOptions
/** @default "NextAuth <no-reply@example.com>" */
from?: string
/**

View File

@@ -19,12 +19,12 @@ Providers.Email({
from: "path/from",
})
// $ExpectType CredentialsConfig<{ username: { label: string; type: string; }; password: { label: string; type: string; }; }>
// $ExpectType CredentialsConfig<{}>
Providers.Credentials({
id: "login",
name: "account",
credentials: {
username: {
user: {
label: "Password",
type: "password",
},
@@ -33,7 +33,7 @@ Providers.Credentials({
type: "password",
},
},
authorize: async ({ username, password }) => {
authorize: async (credentials) => {
const user = {
/* fetched user */
}
@@ -152,13 +152,6 @@ Providers.Okta({
domain: "https://foo.auth0.com",
})
// $ExpectType OAuthConfig<Profile>
Providers.OneLogin({
clientId: "foo123",
clientSecret: "bar123",
domain: "foo.onelogin.com",
})
// $ExpectType OAuthConfig<Profile>
Providers.BattleNet({
clientId: "foo123",
@@ -264,9 +257,3 @@ Providers.Zoho({
clientId: "foo123",
clientSecret: "bar123",
})
// $ExpectType OAuthConfig<Profile>
Providers.Freshbooks({
clientId: "foo123",
clientSecret: "bar123",
})

View File

@@ -13,16 +13,15 @@ You can find the full schema in the table structure section below.
## Getting Started
1. Install `next-auth` and `@next-auth/dynamodb-adapter`
1. Install `next-auth` and `@next-auth/dynamodb-adapter@canary`
```js
npm install next-auth @next-auth/dynamodb-adapter
npm install next-auth @next-auth/dynamodb-adapter@canary
```
2. Add this adapter to your `pages/api/auth/[...nextauth].js` next-auth configuration object.
You need to pass `DocumentClient` instance from `aws-sdk` to the adapter.
The default table name is `next-auth`, but you can customise that by passing `{ tableName: 'your-table-name' }` as the second parameter in the adapter.
You need to pass `aws-sdk` to the adapter in addition to the table name.
```javascript title="pages/api/auth/[...nextauth].js"
import AWS from "aws-sdk";
@@ -49,9 +48,10 @@ export default NextAuth({
}),
// ...add more providers here
],
adapter: DynamoDBAdapter(
new AWS.DynamoDB.DocumentClient()
),
adapter: DynamoDBAdapter({
AWS,
tableName: "next-auth-test",
}),
...
});
```

View File

@@ -11,10 +11,10 @@ You can find the Fauna schema and seed information in the docs at [next-auth.js.
## Getting Started
1. Install `next-auth` and `@next-auth/fauna-adapter`
1. Install `next-auth` and `@next-auth/fauna-adapter@canary`
```js
npm install next-auth @next-auth/fauna-adapter
npm install next-auth @next-auth/fauna-adapter@canary
```
2. Add this adapter to your `pages/api/[...nextauth].js` next-auth configuration object.
@@ -49,8 +49,6 @@ export default NextAuth({
## Schema
Run the following commands inside of the `Shell` tab in the Fauna dashboard to setup the appropriate collections and indexes.
```javascript
CreateCollection({ name: "accounts" })
CreateCollection({ name: "sessions" })
@@ -78,7 +76,7 @@ CreateIndex({
terms: [{ field: ["data", "email"] }],
})
CreateIndex({
name: "verification_request_by_token_and_identifier",
name: "verification_request_by_token",
source: Collection("verification_requests"),
unique: true,
terms: [{ field: ["data", "token"] }, { field: ["data", "identifier"] }],

View File

@@ -9,13 +9,13 @@ This is the Firebase Adapter for [`next-auth`](https://next-auth.js.org). This p
## Getting Started
1. Install `next-auth` and `@next-auth/firebase-adapter`
1. Install `next-auth` and `@next-auth/firebase-adapter@canary`
```js
npm install next-auth @next-auth/firebase-adapter
npm install next-auth @next-auth/firebase-adapter@canary
```
2. Add this adapter to your `pages/api/auth/[...nextauth].js` next-auth configuration object.
2. Add this adapter to your `pages/api/[...nextauth].js` next-auth configuration object.
```javascript title="pages/api/auth/[...nextauth].js"
import NextAuth from "next-auth"

View File

@@ -11,7 +11,6 @@ All table/collection names in the built in models are plural, and all table name
You can [extend the built in models](/tutorials/typeorm-custom-models) and even [create your own database adapter](/tutorials/creating-a-database-adapter) if you want to use NextAuth.js with a database that is not supported out of the box.
:::
---
## User
@@ -30,7 +29,7 @@ If a user first signs in with OAuth then their email address is automatically po
This provides a way to contact users and for users to maintain access to their account and sign in using email in the event they are unable to sign in with the OAuth provider in future (if email sign in is configured).
:::
## Account
## Account
Table: `accounts`
@@ -60,4 +59,4 @@ The Verification Request model is used to store tokens for passwordless sign in
A single User can have multiple open Verification Requests (e.g. to sign in to different devices).
It has been designed to be extendable for other verification purposes in future (e.g. 2FA / short codes).
It has been designed to be extendable for other verification purposes in future (e.g. 2FA / short codes).

View File

@@ -13,10 +13,10 @@ Depending on your architecture you can use PouchDB's http adapter to reach any d
> **Prerequesite**: Your PouchDB instance MUST provide the `pouchdb-find` plugin since it is used internally by the adapter to build and manage indexes
1. Install `next-auth` and `@next-auth/pouchdb-adapter`
1. Install `next-auth` and `@next-auth/pouchdb-adapter@canary`
```js
npm install next-auth @next-auth/pouchdb-adapter
npm install next-auth @next-auth/pouchdb-adapter@canary
```
2. Add this adapter to your `pages/api/auth/[...nextauth].js` next-auth configuration object

View File

@@ -11,10 +11,10 @@ You can also use NextAuth.js with the new experimental Adapter for [Prisma](http
You may have noticed there is a `prisma` and `prisma-legacy` adapter. This is due to historical reasons, but the code has mostly converged so that there is no longer much difference between the two. The legacy adapter, however, does have the ability to rename tables which the newer version does not.
:::
To use this Adapter, you need to install Prisma Client, Prisma CLI, and the separate `@next-auth/prisma-adapter` package:
To use this Adapter, you need to install Prisma Client, Prisma CLI, and the separate `@next-auth/prisma-adapter@canary` package:
```
npm install @prisma/client @next-auth/prisma-adapter
npm install @prisma/client @next-auth/prisma-adapter@canary
npm install prisma --save-dev
```

View File

@@ -19,4 +19,4 @@ Objects stored in MongoDB use similar datatypes to SQL, with some differences:
4. A sparse index is used on the User `email` property to allow it to be optional, while still enforcing uniqueness if it is specified.
This is functionally equivalent to the ANSI SQL behaviour for a `unique` but `nullable` property.
This is functionally equivalent to the ANSI SQL behaviour for a `unique` but `nullable` property.

View File

@@ -1,88 +1,88 @@
---
id: mssql
title: Microsoft SQL Server
---
Schema for a Microsoft SQL Server (mssql) database.
:::note
When using a Microsoft SQL Server database with the default adapter (TypeORM) all properties of type `timestamp` are transformed to `datetime`.
This transform is also applied to any properties of type `timestamp` when using custom models.
:::
```sql
CREATE TABLE accounts
(
id int IDENTITY(1,1) NOT NULL,
compound_id varchar(255) NOT NULL,
user_id int NOT NULL,
provider_type varchar(255) NOT NULL,
provider_id varchar(255) NOT NULL,
provider_account_id varchar(255) NOT NULL,
refresh_token text NULL,
access_token text NULL,
access_token_expires datetime NULL,
created_at datetime NOT NULL DEFAULT getdate(),
updated_at datetime NOT NULL DEFAULT getdate()
);
CREATE TABLE sessions
(
id int IDENTITY(1,1) NOT NULL,
user_id int NOT NULL,
expires datetime NOT NULL,
session_token varchar(255) NOT NULL,
access_token varchar(255) NOT NULL,
created_at datetime NOT NULL DEFAULT getdate(),
updated_at datetime NOT NULL DEFAULT getdate()
);
CREATE TABLE users
(
id int IDENTITY(1,1) NOT NULL,
name varchar(255) NULL,
email varchar(255) NULL,
email_verified datetime NULL,
image varchar(255) NULL,
created_at datetime NOT NULL DEFAULT getdate(),
updated_at datetime NOT NULL DEFAULT getdate()
);
CREATE TABLE verification_requests
(
id int IDENTITY(1,1) NOT NULL,
identifier varchar(255) NOT NULL,
token varchar(255) NOT NULL,
expires datetime NOT NULL,
created_at datetime NOT NULL DEFAULT getdate(),
updated_at datetime NOT NULL DEFAULT getdate()
);
CREATE UNIQUE INDEX compound_id
ON accounts(compound_id);
CREATE INDEX provider_account_id
ON accounts(provider_account_id);
CREATE INDEX provider_id
ON accounts(provider_id);
CREATE INDEX user_id
ON accounts(user_id);
CREATE UNIQUE INDEX session_token
ON sessions(session_token);
CREATE UNIQUE INDEX access_token
ON sessions(access_token);
CREATE UNIQUE INDEX email
ON users(email);
CREATE UNIQUE INDEX token
ON verification_requests(token);
```
When using NextAuth.js with SQL Server for the first time, run NextAuth.js once against your database with `?synchronize=true` on the connection string and export the schema that is created.
:::
---
id: mssql
title: Microsoft SQL Server
---
Schema for a Microsoft SQL Server (mssql) database.
:::note
When using a Microsoft SQL Server database with the default adapter (TypeORM) all properties of type `timestamp` are transformed to `datetime`.
This transform is also applied to any properties of type `timestamp` when using custom models.
:::
```sql
CREATE TABLE accounts
(
id int IDENTITY(1,1) NOT NULL,
compound_id varchar(255) NOT NULL,
user_id int NOT NULL,
provider_type varchar(255) NOT NULL,
provider_id varchar(255) NOT NULL,
provider_account_id varchar(255) NOT NULL,
refresh_token text NULL,
access_token text NULL,
access_token_expires datetime NULL,
created_at datetime NOT NULL DEFAULT getdate(),
updated_at datetime NOT NULL DEFAULT getdate()
);
CREATE TABLE sessions
(
id int IDENTITY(1,1) NOT NULL,
user_id int NOT NULL,
expires datetime NOT NULL,
session_token varchar(255) NOT NULL,
access_token varchar(255) NOT NULL,
created_at datetime NOT NULL DEFAULT getdate(),
updated_at datetime NOT NULL DEFAULT getdate()
);
CREATE TABLE users
(
id int IDENTITY(1,1) NOT NULL,
name varchar(255) NULL,
email varchar(255) NULL,
email_verified datetime NULL,
image varchar(255) NULL,
created_at datetime NOT NULL DEFAULT getdate(),
updated_at datetime NOT NULL DEFAULT getdate()
);
CREATE TABLE verification_requests
(
id int IDENTITY(1,1) NOT NULL,
identifier varchar(255) NOT NULL,
token varchar(255) NOT NULL,
expires datetime NOT NULL,
created_at datetime NOT NULL DEFAULT getdate(),
updated_at datetime NOT NULL DEFAULT getdate()
);
CREATE UNIQUE INDEX compound_id
ON accounts(compound_id);
CREATE INDEX provider_account_id
ON accounts(provider_account_id);
CREATE INDEX provider_id
ON accounts(provider_id);
CREATE INDEX user_id
ON accounts(user_id);
CREATE UNIQUE INDEX session_token
ON sessions(session_token);
CREATE UNIQUE INDEX access_token
ON sessions(access_token);
CREATE UNIQUE INDEX email
ON users(email);
CREATE UNIQUE INDEX token
ON verification_requests(token);
```
When using NextAuth.js with SQL Server for the first time, run NextAuth.js once against your database with `?synchronize=true` on the connection string and export the schema that is created.
:::

View File

@@ -84,4 +84,4 @@ CREATE UNIQUE INDEX email
CREATE UNIQUE INDEX token
ON verification_requests(token);
```
```

View File

@@ -44,7 +44,7 @@ callbacks: {
/**
* @param {object} user User object
* @param {object} account Provider account
* @param {object} profile Provider profile
* @param {object} profile Provider profile
* @return {boolean|string} Return `true` to allow sign in
* Return `false` to deny access
* Return `string` to redirect to (eg.: "/unauthorized")
@@ -64,13 +64,13 @@ callbacks: {
...
```
* When using the **Email Provider** the `signIn()` callback is triggered both when the user makes a **Verification Request** (before they are sent email with a link that will allow them to sign in) and again *after* they activate the link in the sign in email.
- When using the **Email Provider** the `signIn()` callback is triggered both when the user makes a **Verification Request** (before they are sent email with a link that will allow them to sign in) and again _after_ they activate the link in the sign in email.
Email accounts do not have profiles in the same way OAuth accounts do. On the first call during email sign in the `profile` object will include a property `verificationRequest: true` to indicate it is being triggered in the verification request flow. When the callback is invoked _after_ a user has clicked on a sign in link, this property will not be present.
Email accounts do not have profiles in the same way OAuth accounts do. On the first call during email sign in the `profile` object will include a property `verificationRequest: true` to indicate it is being triggered in the verification request flow. When the callback is invoked *after* a user has clicked on a sign in link, this property will not be present.
You can check for the `verificationRequest` property to avoid sending emails to addresses or domains on a blocklist (or to only explicitly generate them for email address in an allow list).
* When using the **Credentials Provider** the `user` object is the response returned from the `authorize` callback and the `profile` object is the raw body of the `HTTP POST` submission.
- When using the **Credentials Provider** the `user` object is the response returned from the `authorization` callback and the `profile` object is the raw body of the `HTTP POST` submission.
:::note
When using NextAuth.js with a database, the User object will be either a user object from the database (including the User ID) if the user has signed in before or a simpler prototype user object (i.e. name, email, image) for users who have not signed in before.
@@ -78,12 +78,6 @@ When using NextAuth.js with a database, the User object will be either a user ob
When using NextAuth.js without a database, the user object it will always be a prototype user object, with information extracted from the profile.
:::
:::note
Redirects returned by this callback cancel the authentication flow. Only redirect to error pages that, for example, tell the user why they're not allowed to sign in.
To redirect to a page after a successful sign in, please use [the `callbackUrl` option](/getting-started/client#specifying-a-callbackurl) or [the redirect callback](/configuration/callbacks#redirect-callback).
:::
## Redirect callback
The redirect callback is called anytime the user is redirected to a callback URL (e.g. on signin or signout).
@@ -107,22 +101,21 @@ callbacks: {
...
```
:::note
The redirect callback may be invoked more than once in the same flow.
:::
## JWT callback
This JSON Web Token callback is called whenever a JSON Web Token is created (i.e. at sign
This JSON Web Token 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).
e.g. `/api/auth/signin`, `getSession()`, `useSession()`, `/api/auth/session`
* As with database session expiry times, token expiry time is extended whenever a session is active.
* The arguments *user*, *account*, *profile* and *isNewUser* are only passed the first time this callback is called on a new session, after the user signs in.
- As with database session expiry times, token expiry time is extended whenever a session is active.
- The arguments _user_, _account_, _profile_ and _isNewUser_ are only passed the first time this callback is called on a new session, after the user signs in.
The contents *user*, *account*, *profile* and *isNewUser* will vary depending on the provider and on if you are using a database or not. If you want to pass data such as User ID, OAuth Access Token, etc. to the browser, you can persist it in the token and use the `session()` callback to return it.
The contents _user_, _account_, _profile_ and _isNewUser_ will vary depending on the provider and on if you are using a database or not. If you want to pass data such as User ID, OAuth Access Token, etc. to the browser, you can persist it in the token and use the `session()` callback to return it.
```js title="pages/api/auth/[...nextauth].js"
...
@@ -167,8 +160,8 @@ The session callback is called whenever a session is checked. By default, only a
e.g. `getSession()`, `useSession()`, `/api/auth/session`
* When using database sessions, the User object is passed as an argument.
* When using JSON Web Tokens for sessions, the JWT payload is provided instead.
- When using database sessions, the User object is passed as an argument.
- When using JSON Web Tokens for sessions, the JWT payload is provided instead.
```js title="pages/api/auth/[...nextauth].js"
...
@@ -177,7 +170,7 @@ callbacks: {
* @param {object} session Session object
* @param {object} token User object (if using database sessions)
* JSON Web Token (if not using database sessions)
* @return {object} Session that will be returned to the client
* @return {object} Session that will be returned to the client
*/
async session(session, token) {
// Add property to session, like an access_token from a provider.
@@ -188,8 +181,6 @@ callbacks: {
...
```
If you're using TypeScript, you will want to [augment the session type](/getting-started/typescript#module-augmentation).
:::tip
When using JSON Web Tokens the `jwt()` callback is invoked before the `session()` callback, so anything you add to the
JSON Web Token will be immediately available in the session callback, like for example an `access_token` from a provider.

View File

@@ -52,7 +52,6 @@ _e.g. To set a prefix for all table names you can use the **entityPrefix** optio
```js
"mysql://nextauth:password@127.0.0.1:3306/database_name?entityPrefix=nextauth_"
```
_…or as a database configuration object:_

View File

@@ -123,34 +123,27 @@ jwt: {
// Defaults to NextAuth.js secret if not explicitly specified.
// This is used to generate the actual signingKey and produces a warning
// message if not defined explicitly.
// You can generate a secret be using `openssl rand -base64 64`
secret: 'INp8IvdIyeMcoGAgFGoA61DdBglwwSqnXJZkgz8PSnw',
// secret: 'INp8IvdIyeMcoGAgFGoA61DdBglwwSqnXJZkgz8PSnw',
// You can generate a signing key using `jose newkey -s 512 -t oct -a HS512`
// This gives you direct knowledge of the key used to sign the token so you can use it
// to authenticate indirectly (eg. to a database driver)
signingKey: {
kty: "oct",
kid: "Dl893BEV-iVE-x9EC52TDmlJUgGm9oZ99_ZL025Hc5Q",
alg: "HS512",
k: "K7QqRmJOKRK2qcCKV_pi9PSBv3XP0fpTu30TP8xn4w01xR3ZMZM38yL2DnTVPVw6e4yhdh0jtoah-i4c_pZagA"
},
// signingKey: {"kty":"oct","kid":"Dl893BEV-iVE-x9EC52TDmlJUgGm9oZ99_ZL025Hc5Q","alg":"HS512","k":"K7QqRmJOKRK2qcCKV_pi9PSBv3XP0fpTu30TP8xn4w01xR3ZMZM38yL2DnTVPVw6e4yhdh0jtoah-i4c_pZagA"},
// If you chose something other than the default algorithm for the signingKey (HS512)
// you also need to configure the algorithm
verificationOptions: {
algorithms: ['HS256']
},
// verificationOptions: {
// algorithms: ['HS256']
// },
// Set to true to use encryption. Defaults to false (signing only).
encryption: true,
// You can generate an encryption key by using `npx node-jose-tools newkey -s 256 -t oct -a A256GCM -u enc`
encryptionKey: "",
// decryptionKey: encryptionKey,
decryptionOptions: {
algorithms: ['A256GCM']
},
// encryption: true,
// encryptionKey: "",
// decryptionKey = encryptionKey,
// decryptionOptions = {
// algorithms: ['A256GCM']
// },
// You can define your own encode/decode functions for signing and encryption
// if you want to override the default behaviour.
async encode({ secret, token, maxAge }) {},
async decode({ secret, token, maxAge }) {},
// async encode({ secret, token, maxAge }) {},
// async decode({ secret, token, maxAge }) {},
}
```
@@ -236,7 +229,7 @@ pages: {
signOut: '/auth/signout',
error: '/auth/error', // Error code passed in query string as ?error=
verifyRequest: '/auth/verify-request', // (used for check email message)
newUser: '/auth/new-user' // New users will be directed here on first sign in (leave the property out if not of interest)
newUser: null // If set, new users will be directed here on first sign in
}
```

View File

@@ -16,15 +16,17 @@ To add a custom login page, you can use the `pages` option:
signOut: '/auth/signout',
error: '/auth/error', // Error code passed in query string as ?error=
verifyRequest: '/auth/verify-request', // (used for check email message)
newUser: '/auth/new-user' // New users will be directed here on first sign in (leave the property out if not of interest)
newUser: null // If set, new users will be directed here on first sign in
}
...
```
## Error codes
We purposefully restrict the returned error codes for increased security.
### Error page
The following errors are passed as error query parameters to the default or overriden error page:
- **Configuration**: There is a problem with the server configuration. Check if your [options](/configuration/options#options) is correct.
@@ -35,6 +37,7 @@ The following errors are passed as error query parameters to the default or over
Example: `/auth/error?error=Configuration`
### Sign-in page
The following errors are passed as error query parameters to the default or overriden sign-in page:
- **OAuthSignin**: Error in constructing an authorization URL ([1](https://github.com/nextauthjs/next-auth/blob/457952bb5abf08b09861b0e5da403080cd5525be/src/server/lib/signin/oauth.js), [2](https://github.com/nextauthjs/next-auth/blob/main/src/server/lib/oauth/pkce-handler.js), [3](https://github.com/nextauthjs/next-auth/blob/main/src/server/lib/oauth/state-handler.js)),
@@ -60,14 +63,16 @@ By default, the built-in pages will follow the system theme, utilizing the [`pre
In order to get the available authentication providers and the URLs to use for them, you can make a request to the API endpoint `/api/auth/providers`:
```jsx title="pages/auth/signin.js"
import { getProviders, signIn } from 'next-auth/client'
import { getProviders, signIn } from "next-auth/client"
export default function SignIn({ providers }) {
return (
<>
{Object.values(providers).map(provider => (
{Object.values(providers).map((provider) => (
<div key={provider.name}>
<button onClick={() => signIn(provider.id)}>Sign in with {provider.name}</button>
<button onClick={() => signIn(provider.id)}>
Sign in with {provider.name}
</button>
</div>
))}
</>
@@ -75,10 +80,10 @@ export default function SignIn({ providers }) {
}
// This is the recommended way for Next.js 9.3 or newer
export async function getServerSideProps(context){
export async function getServerSideProps(context) {
const providers = await getProviders()
return {
props: { providers }
props: { providers },
}
}
@@ -97,26 +102,26 @@ SignIn.getInitialProps = async () => {
If you create a custom sign in form for email sign in, you will need to submit both fields for the **email** address and **csrfToken** from **/api/auth/csrf** in a POST request to **/api/auth/signin/email**.
```jsx title="pages/auth/email-signin.js"
import { getCsrfToken } from 'next-auth/client'
import { getCsrfToken } from "next-auth/client"
export default function SignIn({ csrfToken }) {
return (
<form method='post' action='/api/auth/signin/email'>
<input name='csrfToken' type='hidden' defaultValue={csrfToken}/>
<form method="post" action="/api/auth/signin/email">
<input name="csrfToken" type="hidden" defaultValue={csrfToken} />
<label>
Email address
<input type='email' id='email' name='email'/>
<input type="email" id="email" name="email" />
</label>
<button type='submit'>Sign in with Email</button>
<button type="submit">Sign in with Email</button>
</form>
)
}
// This is the recommended way for Next.js 9.3 or newer
export async function getServerSideProps(context){
export async function getServerSideProps(context) {
const csrfToken = await getCsrfToken(context)
return {
props: { csrfToken }
props: { csrfToken },
}
}
@@ -133,7 +138,7 @@ SignIn.getInitialProps = async (context) => {
You can also use the `signIn()` function which will handle obtaining the CSRF token for you:
```js
signIn('email', { email: 'jsmith@example.com' })
signIn("email", { email: "jsmith@example.com" })
```
### Credentials Sign in
@@ -141,21 +146,21 @@ signIn('email', { email: 'jsmith@example.com' })
If you create a sign in form for credentials based authentication, you will need to pass a **csrfToken** from **/api/auth/csrf** in a POST request to **/api/auth/callback/credentials**.
```jsx title="pages/auth/credentials-signin.js"
import { getCsrfToken } from 'next-auth/client'
import { getCsrfToken } from "next-auth/client"
export default function SignIn({ csrfToken }) {
return (
<form method='post' action='/api/auth/callback/credentials'>
<input name='csrfToken' type='hidden' defaultValue={csrfToken}/>
<form method="post" action="/api/auth/callback/credentials">
<input name="csrfToken" type="hidden" defaultValue={csrfToken} />
<label>
Username
<input name='username' type='text'/>
<input name="username" type="text" />
</label>
<label>
Password
<input name='password' type='password'/>
<input name="password" type="password" />
</label>
<button type='submit'>Sign in</button>
<button type="submit">Sign in</button>
</form>
)
}
@@ -164,8 +169,8 @@ export default function SignIn({ csrfToken }) {
export async function getServerSideProps(context) {
return {
props: {
csrfToken: await getCsrfToken(context)
}
csrfToken: await getCsrfToken(context),
},
}
}
@@ -182,7 +187,7 @@ SignIn.getInitialProps = async (context) => {
You can also use the `signIn()` function which will handle obtaining the CSRF token for you:
```js
signIn('credentials', { username: 'jsmith', password: '1234' })
signIn("credentials", { username: "jsmith", password: "1234" })
```
:::tip

View File

@@ -187,7 +187,7 @@ You only need to add two changes:
2. Add provider documentation: [`www/docs/providers/{provider}.md`](https://github.com/nextauthjs/next-auth/tree/main/www/docs/providers)
3. Add it to our [provider types](https://github.com/nextauthjs/next-auth/blob/main/types/providers.d.ts) (for TS projects)<br />
• you just need to add your new provider name to [this list](https://github.com/nextauthjs/next-auth/blob/main/types/providers.d.ts#L56-L97)<br />
• in case your new provider accepts some custom options, you can [add them here](https://github.com/nextauthjs/next-auth/blob/main/types/providers.d.ts#L48-L53)
• in case you new provider accepts some custom options, you can [add them here](https://github.com/nextauthjs/next-auth/blob/main/types/providers.d.ts#L48-L53)
That's it! 🎉 Others will be able to discover this provider much more easily now!
@@ -260,14 +260,14 @@ providers: [
// that is false/null if the credentials are invalid.
// e.g. return { id: 1, name: 'J Smith', email: 'jsmith@example.com' }
// You can also use the `req` object to obtain additional parameters
// (i.e., the request IP address)
// (i.e., the request IP address)
const res = await fetch("/your/endpoint", {
method: 'POST',
body: JSON.stringify(credentials),
headers: { "Content-Type": "application/json" }
})
const user = await res.json()
// If no error and we have user data, return it
if (res.ok && user) {
return user
@@ -288,10 +288,10 @@ The Credentials provider can only be used if JSON Web Tokens are enabled for ses
### Options
| Name | Description | Type | Required |
| Name | Description | Type | Required |
| :---------: | :-----------------------------------------------: | :-----------------------------------: | :------: |
| id | Unique ID for the provider | `string` | Yes |
| name | Descriptive name for the provider | `string` | Yes |
| type | Type of provider, in this case `credentials` | `"credentials"` | Yes |
| credentials | The credentials to sign-in with | `Object` | Yes |
| id | Unique ID for the provider | `string` | Yes |
| name | Descriptive name for the provider | `string` | Yes |
| type | Type of provider, in this case `credentials` | `"credentials"` | Yes |
| credentials | The credentials to sign-in with | `Object` | Yes |
| authorize | Callback to execute once user is to be authorized | `(credentials, req) => Promise<User>` | Yes |

View File

@@ -87,20 +87,20 @@ https://next-auth.js.org/errors#jwt_session_error JWKKeySupport: the key does no
The algorithm used for generating your key isn't listed as supported. You can generate a HS512 key using
````
```
jose newkey -s 512 -t oct -a HS512
````
```
If you are unable to use an HS512 key (for example to interoperate with other services) you can define what is supported using
````
```
jwt: {
signingKey: {"kty":"oct","kid":"--","alg":"HS256","k":"--"},
verificationOptions: {
algorithms: ["HS256"]
}
}
````
```
#### SESSION_ERROR

View File

@@ -98,7 +98,7 @@ If you are deploying directly to a particular cloud platform you may also want t
Less serious or edge case issues (e.g. queries about compatibility with optional RFC specifications) can be raised as public issues on GitHub.
If you discover what you think may be a potentially serious security problem, please contact a core team member via a private channel (e.g. via email to me@iaincollins.com or info@balazsorban.com and yo@ndo.dev) or raise a public issue requesting someone get in touch with you via whatever means you prefer for more details.
If you discover what you think may be a potentially serious security problem, please contact a core team member via a private channel (e.g. via email to me@iaincollins.com) or raise a public issue requesting someone get in touch with you via whatever means you prefer for more details.
### What is the disclosure policy for NextAuth.js?
@@ -202,7 +202,7 @@ 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 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.
If using JSON Web Token you should at least specify a secret and ideally configure public/private keys.

View File

@@ -22,7 +22,7 @@ The NextAuth.js client library makes it easy to interact with sessions from Reac
:::tip
The session data returned to the client does not contain sensitive information such as the Session Token or OAuth tokens. It contains a minimal payload that includes enough data needed to display information on a page about the user who is signed in for presentation purposes (e.g name, email, image).
You can use the [session callback](/configuration/callbacks#session-callback) to customize the session object returned to the client if you need to return additional data in the session object.
You can use the [session callback](/configuration/callbacks#session) to customize the session object returned to the client if you need to return additional data in the session object.
:::
---
@@ -206,7 +206,7 @@ e.g.
- `signIn('google', { callbackUrl: 'http://localhost:3000/foo' })`
- `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 hostname, 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). By default it requires the URL to be an absolute URL at the same hostname, or else it will redirect to the homepage. You can define your own redirect callback to allow other URLs, including supporting relative URLs.
#### Using the redirect: false option
@@ -274,7 +274,7 @@ The following parameters are always overridden server-side: `redirect_uri`, `sta
- Client Side: **Yes**
- Server Side: No
In order to logout, use the `signOut()` method to ensure the user ends back on the page they started on after completing the sign out flow. It also handles CSRF tokens for you automatically.
Using the `signOut()` method ensures the user ends back on the page they started on after completing the sign out flow. It also handles CSRF tokens for you automatically.
It reloads the page in the browser when complete.
@@ -290,7 +290,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 hostname (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). By default this means it must be an absolute URL at the same hostname (or else it will default to the homepage); you can define your own custom redirect callback to allow other URLs, including supporting relative URLs.
#### Using the redirect: false option
@@ -346,22 +346,21 @@ If every one of your pages needs to be protected, you can do this in `_app`, oth
The session state is automatically synchronized across all open tabs/windows and they are all updated whenever they gain or lose focus or the state changes in any of them (e.g. a user signs in or out).
If you have session expiry times of 30 days (the default) or more then you probably don't need to change any of the default options in the Provider. If you need to, you can trigger an update of the session object across all tabs/windows by calling `getSession()` from a client side function.
If you have session expiry times of 30 days (the default) or more then you probably don't need to change any of the default options in the Provider. If you need to, you can can trigger an update of the session object across all tabs/windows by calling `getSession()` from a client side function.
However, if you need to customise the session behaviour and/or are using short session expiry times, you can pass options to the provider to customise the behaviour of the `useSession()` hook.
```jsx title="pages/_app.js"
import { Provider } from "next-auth/client"
import { Provider } from 'next-auth/client'
export default function App({ Component, pageProps }) {
export default function App ({ Component, pageProps }) {
return (
<Provider
session={pageProps.session}
<Provider session={pageProps.session}
options={{
clientMaxAge: 60, // Re-fetch session if cache is older than 60 seconds
keepAlive: 5 * 60, // Send keepAlive message every 5 minutes
clientMaxAge: 60 // Re-fetch session if cache is older than 60 seconds
keepAlive: 5 * 60 // Send keepAlive message every 5 minutes
}}
>
>
<Component {...pageProps} />
</Provider>
)
@@ -421,7 +420,7 @@ AdminDashboard.auth = true
```jsx title="pages/_app.jsx"
export default function App({ Component, pageProps }) {
return (
<Provider session={pageProps.session}>
<SessionProvider session={pageProps.session}>
{Component.auth ? (
<Auth>
<Component {...pageProps} />
@@ -429,7 +428,7 @@ export default function App({ Component, pageProps }) {
) : (
<Component {...pageProps} />
)}
</Provider>
</SessionProvider>
)
}

View File

@@ -18,15 +18,15 @@ To add NextAuth.js to a project create a file called `[...nextauth].js` in `page
[Read more about how to add authentication providers.](/configuration/providers)
```javascript title="pages/api/auth/[...nextauth].js"
import NextAuth from 'next-auth'
import Providers from 'next-auth/providers'
import NextAuth from "next-auth"
import Providers from "next-auth/providers"
export default NextAuth({
// Configure one or more authentication providers
providers: [
Providers.GitHub({
clientId: process.env.GITHUB_ID,
clientSecret: process.env.GITHUB_SECRET
clientSecret: process.env.GITHUB_SECRET,
}),
// ...add more providers here
],
@@ -47,21 +47,27 @@ See the [options documentation](/configuration/options) for how to configure pro
The `useSession()` React Hook in the NextAuth.js client is the easiest way to check if someone is signed in.
```jsx title="pages/index.js"
import { signIn, signOut, useSession } from 'next-auth/client'
import { signIn, signOut, useSession } from "next-auth/client"
export default function Page() {
const [ session, loading ] = useSession()
const [session, loading] = useSession()
return <>
{!session && <>
Not signed in <br/>
<button onClick={() => signIn()}>Sign in</button>
</>}
{session && <>
Signed in as {session.user.email} <br/>
<button onClick={() => signOut()}>Sign out</button>
</>}
</>
return (
<>
{!session && (
<>
Not signed in <br />
<button onClick={() => signIn()}>Sign in</button>
</>
)}
{session && (
<>
Signed in as {session.user.email} <br />
<button onClick={() => signOut()}>Sign out</button>
</>
)}
</>
)
}
```
@@ -74,9 +80,9 @@ You can use the `useSession` hook from anywhere in your application (e.g. in a h
To allow session state to be shared between pages - which improves performance, reduces network traffic and avoids component state changes while rendering - you can use the NextAuth.js Provider in `pages/_app.js`.
```jsx title="pages/_app.js"
import { Provider } from 'next-auth/client'
import { Provider } from "next-auth/client"
export default function App ({ Component, pageProps }) {
export default function App({ Component, pageProps }) {
return (
<Provider session={pageProps.session}>
<Component {...pageProps} />

View File

@@ -5,11 +5,11 @@ title: Azure Active Directory B2C
## Documentation
https://docs.microsoft.com/azure/active-directory/develop/v2-oauth2-auth-code-flow
https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow
## Configuration
https://docs.microsoft.com/azure/active-directory-b2c/tutorial-create-tenant
https://docs.microsoft.com/en-us/azure/active-directory-b2c/tutorial-create-tenant
## Options

View File

@@ -1,38 +0,0 @@
---
id: coinbase
title: Coinbase
---
## Documentation
https://developers.coinbase.com/api/v2
## Configuration
https://www.coinbase.com/settings/api
## Options
The **Coinbase Provider** comes with a set of default options:
- [Coinbase Provider options](https://github.com/nextauthjs/next-auth/blob/main/src/providers/coinbase.js)
You can override any of the options to suit your own use case.
## Example
```js
import Providers from `next-auth/providers`
...
providers: [
Providers.Coinbase({
clientId: process.env.COINBASE_CLIENT_ID,
clientSecret: process.env.COINBASE_CLIENT_SECRET
})
]
...
```
:::tip
This Provider template has a 2 hour access token to it. A refresh token is also returned.
:::

View File

@@ -29,15 +29,15 @@ The Credentials provider is specified like other providers, except that you need
1. A `user` object, which indicates the credentials are valid.
If you return an object it will be persisted to the JSON Web Token and the user will be signed in, unless a custom `signIn()` callback is configured that subsequently rejects it.
If you return an object it will be persisted to the JSON Web Token and the user will be signed in, unless a custom `signIn()` callback is configured that subsequently rejects it.
2. Either `false` or `null`, which indicates failure.
If you return `false` or `null` then an error will be displayed advising the user to check their details.
If you return `false` or `null` then an error will be displayed advising the user to check their details.
3. You can throw an Error or a URL (a string).
If you throw an Error, the user will be sent to the error page with the error message as a query parameter. If throw a URL (a string), the user will be redirected to the URL.
If you throw an Error, the user will be sent to the error page with the error message as a query parameter. If throw a URL (a string), the user will be redirected to the URL.
The Credentials provider's `authorize()` method also provides the request object as the second parameter (see example below).
@@ -88,34 +88,43 @@ You can also use them in conjunction with other provider options.
As with all providers, the order you specify them is the order they are displayed on the sign in page.
```js
providers: [
Providers.Credentials({
id: 'domain-login',
name: "Domain Account",
async authorize(credentials, req) {
const user = { /* add function to get user */ }
return user
providers: [
Providers.Credentials({
id: "domain-login",
name: "Domain Account",
async authorize(credentials, req) {
const user = {
/* add function to get user */
}
return user
},
credentials: {
domain: {
label: "Domain",
type: "text ",
placeholder: "CORPNET",
value: "CORPNET",
},
credentials: {
domain: { label: "Domain", type: "text ", placeholder: "CORPNET", value: "CORPNET" },
username: { label: "Username", type: "text ", placeholder: "jsmith" },
password: { label: "Password", type: "password" }
}
}),
Providers.Credentials({
id: 'intranet-credentials',
name: "Two Factor Auth",
async authorize(credentials, req) {
const user = { /* add function to get user */ }
return user
},
credentials: {
email: { label: "Username", type: "text ", placeholder: "jsmith" },
"2fa-key": { label: "2FA Key" }
},
}),
/* ... additional providers ... /*/
]
username: { label: "Username", type: "text ", placeholder: "jsmith" },
password: { label: "Password", type: "password" },
},
}),
Providers.Credentials({
id: "intranet-credentials",
name: "Two Factor Auth",
async authorize(credentials, req) {
const user = {
/* add function to get user */
}
return user
},
credentials: {
email: { label: "Username", type: "text ", placeholder: "jsmith" },
"2fa-key": { label: "2FA Key" },
},
}),
/* ... additional providers ... /*/
]
```
### Example UI
@@ -126,7 +135,8 @@ You can also [use a custom sign in page](/configuration/pages#credentials-sign-i
<Image src="/img/signin-complex.png"/>
export const Image = ({ children, src, alt = '' }) => (
export const Image = ({ children, src, alt = '' }) => (
<div
style={{
padding: '0.2rem',

View File

@@ -13,7 +13,7 @@ The Email provider can be used in conjunction with (or instead of) one or more O
### How it works
On initial sign in, a **Verification Token** is sent to the email address provided. By default this token is valid for 24 hours. If the Verification Token is used within that time (i.e. by clicking on the link in the email) an account is created for the user and they are signed in.
On initial sign in, a **Verification Token** is sent to the email address provided. By default this token is valid for 24 hours. If the Verification Token is used with that time (i.e. by clicking on the link in the email) an account is created for the user and they are signed in.
If someone provides the email address of an _existing account_ when signing in, an email is sent and they are signed into the account associated with that email address when they follow the link in the email.
@@ -187,7 +187,7 @@ const html = ({ url, site, email }) => {
<td align="center" style="padding: 20px 0;">
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center" style="border-radius: 5px;" bgcolor="${buttonBackgroundColor}"><a href="${url}" target="_blank" style="font-size: 18px; font-family: Helvetica, Arial, sans-serif; color: ${buttonTextColor}; text-decoration: none; border-radius: 5px; padding: 10px 20px; border: 1px solid ${buttonBorderColor}; display: inline-block; font-weight: bold;">Sign in</a></td>
<td align="center" style="border-radius: 5px;" bgcolor="${buttonBackgroundColor}"><a href="${url}" target="_blank" style="font-size: 18px; font-family: Helvetica, Arial, sans-serif; color: ${buttonTextColor}; text-decoration: none; text-decoration: none;border-radius: 5px; padding: 10px 20px; border: 1px solid ${buttonBorderColor}; display: inline-block; font-weight: bold;">Sign in</a></td>
</tr>
</table>
</td>

View File

@@ -1,33 +0,0 @@
---
id: freshbooks
title: Freshbooks
---
## Documentation
https://www.freshbooks.com/api/authenticating-with-oauth-2-0-on-the-new-freshbooks-api
## Configuration
https://my.freshbooks.com/#/developer
## Options
The Freshbooks Provider comes with a set of default options:
https://www.freshbooks.com/api/start
You can override any of the options to suit your own use case.
## Example
```js
import Providers from `next-auth/providers`
...
providers: [
Providers.Freshbooks({
clientId: process.env.FRESHBOOKS_CLIENT_ID,
clientSecret: process.env.FRESHBOOKS_CLIENT_SECRET,
})
]
...
```

View File

@@ -34,7 +34,7 @@ providers: [
```
:::warning
Google only provides Refresh Token to an application the first time a user signs in.
Google only provide the Refresh Token to an application the first time a user signs in.
To force Google to re-issue a Refresh Token, the user needs to remove the application from their account and sign in again:
https://myaccount.google.com/permissions

View File

@@ -19,7 +19,7 @@ From the Auth tab get the client ID and client secret. On the same tab, add redi
The **LinkedIn Provider** comes with a set of default options:
- [LinkedIn Provider options](https://github.com/nextauthjs/next-auth/blob/main/src/providers/linkedin.js)
- [LinkedIn Provider options](https://github.com/nextauthjs/next-auth/blob/main/src/providers/linked-in.js)
You can override any of the options to suit your own use case.

View File

@@ -1,34 +0,0 @@
---
id: naver
title: Naver
---
## Documentation
https://developers.naver.com/docs/login/overview/overview.md
## Configuration
https://developers.naver.com/docs/login/api/api.md
## Options
The **Naver Provider** comes with a set of default options:
- [Naver Provider options](https://github.com/nextauthjs/next-auth/blob/main/src/providers/naver.js)
You can override any of the options to suit your own use case.
## Example
```js
import Providers from `next-auth/providers`
...
providers: [
Providers.Naver({
clientId: process.env.NAVER_CLIENT_ID,
clientSecret: process.env.NAVER_CLIENT_SECRET
})
]
...
```

View File

@@ -1,31 +0,0 @@
---
id: onelogin
title: OneLogin
---
## Documentation
https://developers.onelogin.com/openid-connect
## Options
The **OneLogin Provider** comes with a set of default options:
- [OneLogin Provider options](https://github.com/nextauthjs/next-auth/blob/main/src/providers/onelogin.js)
You can override any of the options to suit your own use case.
## Example
```js
import Providers from `next-auth/providers`
...
providers: [
Providers.OneLogin({
clientId: process.env.ONELOGIN_CLIENT_ID,
clientSecret: process.env.ONELOGIN_CLIENT_SECRET,
domain: process.env.ONELOGIN_DOMAIN
})
]
...
```

View File

@@ -51,15 +51,15 @@ To add a custom login page, you can use the `pages` option:
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:
```jsx title="pages/auth/signin.js"
import { getProviders, signIn } from 'next-auth/client'
import { getProviders, signIn } from "next-auth/client"
export default function SignIn({ providers }) {
const [email, setEmail] = useState('')
const [email, setEmail] = useState("")
return (
<>
{Object.values(providers).map((provider) => {
if (provider.id === 'workos') {
if (provider.id === "workos") {
return (
<div key={provider.id}>
<input
@@ -71,14 +71,14 @@ export default function SignIn({ providers }) {
<button
onClick={() =>
signIn(provider.id, undefined, {
domain: email.split('@')[1],
domain: email.split("@")[1],
})
}
>
Sign in with SSO
</button>
</div>
);
)
}
return (
@@ -87,17 +87,17 @@ export default function SignIn({ providers }) {
Sign in with {provider.name}
</button>
</div>
);
)
})}
</>
);
)
}
// This is the recommended way for Next.js 9.3 or newer
export async function getServerSideProps(context){
export async function getServerSideProps(context) {
const providers = await getProviders()
return {
props: { providers }
props: { providers },
}
}

View File

@@ -11,7 +11,7 @@ _New submissions and edits are welcome!_
### [NextJS Authentication Crash Course with NextAuth.js](https://youtu.be/o_wZIVmWteQ)
This tutorial dives in to the ins and outs of NextAuth including email, GitHub, Twitter and integrating with Auth0 in under hour.
This tutorial dives in to the ins and outs of NextAuth including email, Github, Twitter and integrating with Auth0 in under hour.
### [Create your own NextAuth.js Login Pages](https://youtu.be/kB6YNYZ63fw)
@@ -83,10 +83,6 @@ This example shows how to implement a fullstack app in TypeScript with Next.js u
This `dev.to` 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.
### [Introduction to NextAuth.js](https://www.youtube.com/watch?v=npZsJxWntJM)
This is an introductory video to NextAuth.js for beginners. In this video, it is explained how to set up authentication in a few easy steps and add different configurations to make it more robust and secure.
### [Adding Sign in With Apple Next JS](https://thesiddd.com/blog/apple-auth)
This tutorial walks step by step on how to get Sign In with Apple working (both locally and on a deployed website) using NextAuth.js.
@@ -94,7 +90,3 @@ This tutorial walks step by step on how to get Sign In with Apple working (both
### [How to Authenticate Next.js Apps with Twitter & NextAuth.js](https://spacejelly.dev/posts/how-to-authenticate-next-js-apps-with-twitter-nextauth-js/)
Learn how to add Twitter authentication and login to a Next.js app both clientside and serverside with NextAuth.js.
### [Using NextAuth.js with Magic links](https://dev.to/narciero/using-nextauth-js-with-magic-links-df4)
Learn how to use [Magic](https://magic.link) link authentication with [NextAuth.js](https://next-auth.js.org) to enable passwordless authentication without a database.

View File

@@ -3,16 +3,16 @@ id: ldap-auth-example
title: LDAP Authentication
---
NextAuth.js provides the ability to setup a [custom Credential provider](/configuration/providers#sign-in-with-credentials) which we can take advantage of to authenticate users against an existing LDAP server.
NextAuth.js provides the ability to setup a [custom Credential provider](/configuration/providers#sign-in-with-credentials) which we can take advantage of to authenticate users against an existing LDAP server.
You will need an additional dependency, `ldapjs`, which you can install by running `npm install ldapjs`.
Then you must setup the `Providers.Credentials()` provider key like so:
```js title="[...nextauth].js"
const ldap = require("ldapjs");
import NextAuth from "next-auth";
import Providers from "next-auth/providers";
const ldap = require("ldapjs")
import NextAuth from "next-auth"
import Providers from "next-auth/providers"
export default NextAuth({
providers: [
@@ -26,37 +26,37 @@ export default NextAuth({
// You might want to pull this call out so we're not making a new LDAP client on every login attemp
const client = ldap.createClient({
url: process.env.LDAP_URI,
});
})
// Essentially promisify the LDAPJS client.bind function
return new Promise((resolve, reject) => {
client.bind(credentials.username, credentials.password, (error) => {
if (error) {
console.error("Failed");
reject();
console.error("Failed")
reject()
} else {
console.log("Logged in");
console.log("Logged in")
resolve({
username: credentials.username,
password: credentials.password,
});
})
}
});
});
})
})
},
}),
],
callbacks: {
async jwt(token, user, account, profile, isNewUser) {
const isSignIn = user ? true : false;
const isSignIn = user ? true : false
if (isSignIn) {
token.username = user.username;
token.password = user.password;
token.username = user.username
token.password = user.password
}
return token;
return token
},
async session(session, user) {
return { ...session, user: { username: user.username } };
return { ...session, user: { username: user.username } }
},
},
secret: process.env.NEXTAUTH_SECRET,
@@ -64,19 +64,19 @@ export default NextAuth({
secret: process.env.NEXTAUTH_SECRET,
encryption: true, // Very important to encrypt the JWT, otherwise you're leaking username+password into the browser
},
});
})
```
The idea is that once one is authenticated with the LDAP server, one can pass through both the username/DN and password to the JWT stored in the browser.
The idea is that once one is authenticated with the LDAP server, one can pass through both the username/DN and password to the JWT stored in the browser.
This is then passed back to any API routes and retrieved as such:
```js title="/pages/api/doLDAPWork.js"
token = await jwt.getToken({
req,
secret: process.env.NEXTAUTH_SECRET,
});
const {username, password} = token;
req,
secret: process.env.NEXTAUTH_SECRET,
})
const { username, password } = token
```
> Thanks to [Winwardo](https://github.com/Winwardo) for the code example

View File

@@ -48,7 +48,7 @@ export default {
```
:::note
[View source for built-in TypeORM models and schemas](https://github.com/nextauthjs/adapters/tree/main/packages/typeorm-legacy/src/models)
[View source for built-in TypeORM models and schemas](https://github.com/nextauthjs/adapters/tree/canary/packages/typeorm-legacy/src/models)
:::
## Using custom models

View File

@@ -46,32 +46,4 @@ You can use [node-jose-tools](https://www.npmjs.com/package/node-jose-tools) to
**Option 2**: Specify custom encode/decode functions on the jwt object. This gives you complete control over signing / verification / etc.
#### JWT_AUTO_GENERATED_ENCRYPTION_KEY
#### SIGNIN_CALLBACK_REJECT_REDIRECT
You returned something in the `signIn` callback, that is being deprecated.
You probably had something similar in the callback:
```js
return Promise.reject("/some/url")
```
or
```js
throw "/some/url"
```
To remedy this, simply return the url instead:
```js
return "/some/url"
```
#### STATE_OPTION_DEPRECATION
You provided `state: true` or `state: false` as a provider option. This is being deprecated in a later release in favour of `protection: "state"` and `protection: "none"` respectively. To remedy this warning:
- If you use `state: true`, just simply remove it. The default is `protection: "state"` already..
- If you use `state: false`, set `protection: "none"`.
#### JWT_AUTO_GENERATED_ENCRYPTION_KEY

View File

@@ -42,12 +42,12 @@ module.exports = {
position: "left",
},
{
to: "https://www.npmjs.com/package/next-auth",
href: "https://www.npmjs.com/package/next-auth",
label: "npm",
position: "right",
},
{
to: "https://github.com/nextauthjs/next-auth",
href: "https://github.com/nextauthjs/next-auth",
label: "GitHub",
position: "right",
},
@@ -69,8 +69,12 @@ module.exports = {
to: "/getting-started/introduction",
},
{
label: "Next documentation",
to: "https://next-auth-git-next.nextauthjs.vercel.app",
label: "Contributors",
to: "/contributors",
},
{
label: "Canary documentation",
to: "https://next-auth-git-canary.nextauthjs.vercel.app/",
},
],
},
@@ -91,12 +95,8 @@ module.exports = {
title: "Acknowledgements",
items: [
{
label: "Contributors",
to: "/contributors",
},
{
label: "Sponsors",
to: "https://opencollective.com/nextauth",
label: "Docusaurus",
to: "https://v2.docusaurus.io/",
},
{
label: "Images by unDraw",
@@ -118,13 +118,6 @@ module.exports = {
],
copyright: "NextAuth.js &copy; Iain Collins 2021",
},
colorMode: {
respectPrefersColorScheme: true,
switchConfig: {
darkIcon: "🌑",
lightIcon: "💡",
},
},
},
presets: [
[

View File

@@ -1,10 +1,34 @@
{
"redirects": [
{ "source": "/schemas/models", "destination": "/adapters/models", "permanent": true },
{ "source": "/schemas/mysql", "destination": "/adapters/typeorm/mysql", "permanent": true },
{ "source": "/schemas/postgres", "destination": "/adapters/typeorm/postgres", "permanent": true },
{ "source": "/schemas/mssql", "destination": "/adapters/typeorm/mssql", "permanent": true },
{ "source": "/schemas/mongodb", "destination": "/adapters/typeorm/mongodb", "permanent": true },
{ "source": "/schemas/adapters", "destination": "/adapters/overview", "permanent": true }
{
"source": "/schemas/models",
"destination": "/adapters/models",
"permanent": true
},
{
"source": "/schemas/mysql",
"destination": "/adapters/typeorm/mysql",
"permanent": true
},
{
"source": "/schemas/postgres",
"destination": "/adapters/typeorm/postgres",
"permanent": true
},
{
"source": "/schemas/mssql",
"destination": "/adapters/typeorm/mssql",
"permanent": true
},
{
"source": "/schemas/mongodb",
"destination": "/adapters/typeorm/mongodb",
"permanent": true
},
{
"source": "/schemas/adapters",
"destination": "/adapters/overview",
"permanent": true
}
]
}