mirror of
https://github.com/SrIzan10/next-auth.git
synced 2026-05-01 10:55:20 +00:00
Compare commits
42 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d73daf343 | ||
|
|
3470b582a1 | ||
|
|
6d127e33fc | ||
|
|
29db75ad28 | ||
|
|
d348ca1dc1 | ||
|
|
d53e1ea6c4 | ||
|
|
e701342b1a | ||
|
|
8a133bf5fd | ||
|
|
35a3ea6620 | ||
|
|
289800fbb4 | ||
|
|
28eccc3e64 | ||
|
|
e16bf939a9 | ||
|
|
9b078c92b2 | ||
|
|
87f6f576b1 | ||
|
|
50584bdc4c | ||
|
|
b4429235c0 | ||
|
|
e1b297d06d | ||
|
|
ab764e3793 | ||
|
|
c8941e4b3e | ||
|
|
ead715219a | ||
|
|
8faa7553dd | ||
|
|
90a6a0084b | ||
|
|
cb844a2436 | ||
|
|
74558d6cc2 | ||
|
|
d03125a77b | ||
|
|
66d16f8bf4 | ||
|
|
be74dd0e7e | ||
|
|
9bf867ddcf | ||
|
|
0f460c22da | ||
|
|
887cb00877 | ||
|
|
75ca097ff7 | ||
|
|
bcb9383aec | ||
|
|
b953963101 | ||
|
|
4649f1968b | ||
|
|
45f4a69a4e | ||
|
|
2155c93a3c | ||
|
|
d5958571a4 | ||
|
|
ebecaa6a4b | ||
|
|
1c5173a818 | ||
|
|
35ce332cc6 | ||
|
|
ec295287f1 | ||
|
|
46978ac02f |
7
.gitignore
vendored
7
.gitignore
vendored
@@ -61,4 +61,9 @@ app/yarn.lock
|
||||
/prisma/migrations
|
||||
|
||||
# Tests
|
||||
/coverage
|
||||
/coverage
|
||||
|
||||
# v4
|
||||
packages
|
||||
apps
|
||||
docs/providers.json
|
||||
@@ -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. 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 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.
|
||||
|
||||
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
|
||||
|
||||
126
README.md
126
README.md
@@ -38,7 +38,7 @@ It is designed from the ground up to support Next.js and Serverless.
|
||||
npm install --save next-auth
|
||||
```
|
||||
|
||||
The easiest way to continue getting started, is to follow the [getting started](https://next-auth.js.org/getting-started/example) section in our docs.
|
||||
The easiest way to continue getting started, is to follow the [getting started](https://next-auth.js.org/getting-started/example) section in our docs.
|
||||
|
||||
We also have a section of [tutorials](https://next-auth.js.org/tutorials) for those looking for more specific examples.
|
||||
|
||||
@@ -48,36 +48,36 @@ See [next-auth.js.org](https://next-auth.js.org) for more information and docume
|
||||
|
||||
### Flexible and easy to use
|
||||
|
||||
* Designed to work with any OAuth service, it supports OAuth 1.0, 1.0A and 2.0
|
||||
* Built-in support for [many popular sign-in services](https://next-auth.js.org/configuration/providers)
|
||||
* Supports email / passwordless authentication
|
||||
* Supports stateless authentication with any backend (Active Directory, LDAP, etc)
|
||||
* Supports both JSON Web Tokens and database sessions
|
||||
* Designed for Serverless but runs anywhere (AWS Lambda, Docker, Heroku, etc…)
|
||||
- Designed to work with any OAuth service, it supports OAuth 1.0, 1.0A and 2.0
|
||||
- Built-in support for [many popular sign-in services](https://next-auth.js.org/configuration/providers)
|
||||
- Supports email / passwordless authentication
|
||||
- Supports stateless authentication with any backend (Active Directory, LDAP, etc)
|
||||
- Supports both JSON Web Tokens and database sessions
|
||||
- Designed for Serverless but runs anywhere (AWS Lambda, Docker, Heroku, etc…)
|
||||
|
||||
### Own your own data
|
||||
|
||||
NextAuth.js can be used with or without a database.
|
||||
|
||||
* An open source solution that allows you to keep control of your data
|
||||
* Supports Bring Your Own Database (BYOD) and can be used with any database
|
||||
* Built-in support for [MySQL, MariaDB, Postgres, Microsoft SQL Server, MongoDB and SQLite](https://next-auth.js.org/configuration/databases)
|
||||
* Works great with databases from popular hosting providers
|
||||
* Can also be used *without a database* (e.g. OAuth + JWT)
|
||||
- An open source solution that allows you to keep control of your data
|
||||
- Supports Bring Your Own Database (BYOD) and can be used with any database
|
||||
- Built-in support for [MySQL, MariaDB, Postgres, Microsoft SQL Server, MongoDB and SQLite](https://next-auth.js.org/configuration/databases)
|
||||
- Works great with databases from popular hosting providers
|
||||
- Can also be used _without a database_ (e.g. OAuth + JWT)
|
||||
|
||||
### Secure by default
|
||||
|
||||
* Promotes the use of passwordless sign in mechanisms
|
||||
* Designed to be secure by default and encourage best practice for safeguarding user data
|
||||
* Uses Cross Site Request Forgery Tokens on POST routes (sign in, sign out)
|
||||
* Default cookie policy aims for the most restrictive policy appropriate for each cookie
|
||||
* When JSON Web Tokens are enabled, they are signed by default (JWS) with HS512
|
||||
* Use JWT encryption (JWE) by setting the option `encryption: true` (defaults to A256GCM)
|
||||
* Auto-generates symmetric signing and encryption keys for developer convenience
|
||||
* Features tab/window syncing and keepalive messages to support short lived sessions
|
||||
* Attempts to implement the latest guidance published by [Open Web Application Security Project](https://owasp.org/)
|
||||
- Promotes the use of passwordless sign in mechanisms
|
||||
- Designed to be secure by default and encourage best practice for safeguarding user data
|
||||
- Uses Cross Site Request Forgery Tokens on POST routes (sign in, sign out)
|
||||
- Default cookie policy aims for the most restrictive policy appropriate for each cookie
|
||||
- When JSON Web Tokens are enabled, they are signed by default (JWS) with HS512
|
||||
- Use JWT encryption (JWE) by setting the option `encryption: true` (defaults to A256GCM)
|
||||
- Auto-generates symmetric signing and encryption keys for developer convenience
|
||||
- Features tab/window syncing and keepalive messages to support short lived sessions
|
||||
- Attempts to implement the latest guidance published by [Open Web Application Security Project](https://owasp.org/)
|
||||
|
||||
Advanced options allow you to define your own routines to handle controlling what accounts are allowed to sign in, for encoding and decoding JSON Web Tokens and to set custom cookie security policies and session properties, so you can control who is able to sign in and how often sessions have to be re-validated.
|
||||
Advanced options allow you to define your own routines to handle controlling what accounts are allowed to sign in, for encoding and decoding JSON Web Tokens and to set custom cookie security policies and session properties, so you can control who is able to sign in and how often sessions have to be re-validated.
|
||||
|
||||
### TypeScript
|
||||
|
||||
@@ -90,50 +90,52 @@ The package at `@types/next-auth` is now deprecated.
|
||||
### Add API Route
|
||||
|
||||
```javascript
|
||||
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({
|
||||
providers: [
|
||||
// OAuth authentication providers
|
||||
Providers.Apple({
|
||||
clientId: process.env.APPLE_ID,
|
||||
clientSecret: process.env.APPLE_SECRET
|
||||
clientSecret: process.env.APPLE_SECRET,
|
||||
}),
|
||||
Providers.Google({
|
||||
clientId: process.env.GOOGLE_ID,
|
||||
clientSecret: process.env.GOOGLE_SECRET
|
||||
clientSecret: process.env.GOOGLE_SECRET,
|
||||
}),
|
||||
// Sign in with passwordless email link
|
||||
Providers.Email({
|
||||
server: process.env.MAIL_SERVER,
|
||||
from: '<no-reply@example.com>'
|
||||
from: "<no-reply@example.com>",
|
||||
}),
|
||||
],
|
||||
// SQL or MongoDB database (or leave empty)
|
||||
database: process.env.DATABASE_URL
|
||||
database: process.env.DATABASE_URL,
|
||||
})
|
||||
```
|
||||
|
||||
### Add React Component
|
||||
|
||||
```javascript
|
||||
import {
|
||||
useSession, signIn, signOut
|
||||
} from 'next-auth/client'
|
||||
import { useSession, signIn, signOut } from "next-auth/client"
|
||||
|
||||
export default function Component() {
|
||||
const [ session, loading ] = useSession()
|
||||
if(session) {
|
||||
return <>
|
||||
Signed in as {session.user.email} <br/>
|
||||
<button onClick={() => signOut()}>Sign out</button>
|
||||
</>
|
||||
const [session, loading] = useSession()
|
||||
if (session) {
|
||||
return (
|
||||
<>
|
||||
Signed in as {session.user.email} <br />
|
||||
<button onClick={() => signOut()}>Sign out</button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
return <>
|
||||
Not signed in <br/>
|
||||
<button onClick={() => signIn()}>Sign in</button>
|
||||
</>
|
||||
return (
|
||||
<>
|
||||
Not signed in <br />
|
||||
<button onClick={() => signIn()}>Sign in</button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
@@ -145,14 +147,44 @@ 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">
|
||||
<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>
|
||||
<a href="https://vercel.com?utm_source=nextauthjs&utm_campaign=oss"></a>
|
||||
</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
|
||||
|
||||
We're open to all community contributions! If you'd like to contribute in any way, please first read our [Contributing Guide](https://github.com/nextauthjs/next-auth/blob/canary/CONTRIBUTING.md).
|
||||
|
||||
10
SECURITY.md
10
SECURITY.md
@@ -14,11 +14,11 @@ We request that you contact us directly to report serious issues that might impa
|
||||
|
||||
If you contact us regarding a serious issue:
|
||||
|
||||
* We will endeavor to get back to you within 72 hours.
|
||||
* We will aim to publish a fix within 30 days.
|
||||
* 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.
|
||||
- We will endeavor to get back to you within 72 hours.
|
||||
- We will aim to publish a fix within 30 days.
|
||||
- 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 emailing me@iaincollins.com
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
100
package-lock.json
generated
100
package-lock.json
generated
@@ -1,11 +1,12 @@
|
||||
{
|
||||
"name": "next-auth",
|
||||
"version": "0.0.0-semantically-released",
|
||||
"version": "3.29.10",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"version": "0.0.0-semantically-released",
|
||||
"name": "next-auth",
|
||||
"version": "3.29.10",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@@ -15,8 +16,8 @@
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.14.0",
|
||||
"@next-auth/prisma-legacy-adapter": "0.0.1-canary.127",
|
||||
"@next-auth/typeorm-legacy-adapter": "0.0.2-canary.129",
|
||||
"@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",
|
||||
@@ -2779,9 +2780,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next-auth/prisma-legacy-adapter": {
|
||||
"version": "0.0.1-canary.127",
|
||||
"resolved": "https://registry.npmjs.org/@next-auth/prisma-legacy-adapter/-/prisma-legacy-adapter-0.0.1-canary.127.tgz",
|
||||
"integrity": "sha512-Pd2Y8b1ibDywrndbj3751VNKv1mVcg2w0uNIi01EBVkm5pqA1X+VnKWbPeHfh4arLYw93RPCvfLbWBZS7J1gZQ==",
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@next-auth/prisma-legacy-adapter/-/prisma-legacy-adapter-0.1.2.tgz",
|
||||
"integrity": "sha512-QpGcRvrnFERkvVFroqS89qDbnskw9AjhvWxO095u3xl/8QVI++Y+doluQdZWuV6PewjrV7xY+uVUEnRhnGM8yQ==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.14.0"
|
||||
},
|
||||
@@ -2791,9 +2792,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next-auth/typeorm-legacy-adapter": {
|
||||
"version": "0.0.2-canary.129",
|
||||
"resolved": "https://registry.npmjs.org/@next-auth/typeorm-legacy-adapter/-/typeorm-legacy-adapter-0.0.2-canary.129.tgz",
|
||||
"integrity": "sha512-xEGz3TzBzz+5nXQ6BnC++KGfxTOAgztL32ZRLq47UKz9M0kFBP6pCMJjTszltsBHYUI/Wac2IG2egMTpHtppiQ==",
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@next-auth/typeorm-legacy-adapter/-/typeorm-legacy-adapter-0.1.4.tgz",
|
||||
"integrity": "sha512-UDnctrPiUU0yaPNeFhD6uw0FAuWGvx6IUVwUYTEzZm26RCEe/BBjkpGLYk43M3rcnkBPfkiljomAs/O7Uzh12w==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.14.0",
|
||||
"require_optional": "^1.0.1",
|
||||
@@ -13090,6 +13091,51 @@
|
||||
"react-dom": "^16.13.1 || ^17"
|
||||
}
|
||||
},
|
||||
"node_modules/next-auth/node_modules/@next-auth/prisma-legacy-adapter": {
|
||||
"version": "0.0.1-canary.127",
|
||||
"resolved": "https://registry.npmjs.org/@next-auth/prisma-legacy-adapter/-/prisma-legacy-adapter-0.0.1-canary.127.tgz",
|
||||
"integrity": "sha512-Pd2Y8b1ibDywrndbj3751VNKv1mVcg2w0uNIi01EBVkm5pqA1X+VnKWbPeHfh4arLYw93RPCvfLbWBZS7J1gZQ==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.14.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@prisma/client": "^2.16.1",
|
||||
"next-auth": "^3.17.2"
|
||||
}
|
||||
},
|
||||
"node_modules/next-auth/node_modules/@next-auth/typeorm-legacy-adapter": {
|
||||
"version": "0.0.2-canary.129",
|
||||
"resolved": "https://registry.npmjs.org/@next-auth/typeorm-legacy-adapter/-/typeorm-legacy-adapter-0.0.2-canary.129.tgz",
|
||||
"integrity": "sha512-xEGz3TzBzz+5nXQ6BnC++KGfxTOAgztL32ZRLq47UKz9M0kFBP6pCMJjTszltsBHYUI/Wac2IG2egMTpHtppiQ==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.14.0",
|
||||
"require_optional": "^1.0.1",
|
||||
"typeorm": "^0.2.30"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"mongodb": "^3.5.9",
|
||||
"mssql": "^6.2.1",
|
||||
"mysql": "^2.18.1",
|
||||
"next-auth": "^3.1.0",
|
||||
"pg": "^8.2.1"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"mongodb": {
|
||||
"optional": true
|
||||
},
|
||||
"mssql": {
|
||||
"optional": true
|
||||
},
|
||||
"mysql": {
|
||||
"optional": true
|
||||
},
|
||||
"pg": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/next/node_modules/@babel/runtime": {
|
||||
"version": "7.12.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.5.tgz",
|
||||
@@ -21006,17 +21052,17 @@
|
||||
}
|
||||
},
|
||||
"@next-auth/prisma-legacy-adapter": {
|
||||
"version": "0.0.1-canary.127",
|
||||
"resolved": "https://registry.npmjs.org/@next-auth/prisma-legacy-adapter/-/prisma-legacy-adapter-0.0.1-canary.127.tgz",
|
||||
"integrity": "sha512-Pd2Y8b1ibDywrndbj3751VNKv1mVcg2w0uNIi01EBVkm5pqA1X+VnKWbPeHfh4arLYw93RPCvfLbWBZS7J1gZQ==",
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@next-auth/prisma-legacy-adapter/-/prisma-legacy-adapter-0.1.2.tgz",
|
||||
"integrity": "sha512-QpGcRvrnFERkvVFroqS89qDbnskw9AjhvWxO095u3xl/8QVI++Y+doluQdZWuV6PewjrV7xY+uVUEnRhnGM8yQ==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.14.0"
|
||||
}
|
||||
},
|
||||
"@next-auth/typeorm-legacy-adapter": {
|
||||
"version": "0.0.2-canary.129",
|
||||
"resolved": "https://registry.npmjs.org/@next-auth/typeorm-legacy-adapter/-/typeorm-legacy-adapter-0.0.2-canary.129.tgz",
|
||||
"integrity": "sha512-xEGz3TzBzz+5nXQ6BnC++KGfxTOAgztL32ZRLq47UKz9M0kFBP6pCMJjTszltsBHYUI/Wac2IG2egMTpHtppiQ==",
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@next-auth/typeorm-legacy-adapter/-/typeorm-legacy-adapter-0.1.4.tgz",
|
||||
"integrity": "sha512-UDnctrPiUU0yaPNeFhD6uw0FAuWGvx6IUVwUYTEzZm26RCEe/BBjkpGLYk43M3rcnkBPfkiljomAs/O7Uzh12w==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.14.0",
|
||||
"require_optional": "^1.0.1",
|
||||
@@ -29139,6 +29185,28 @@
|
||||
"preact": "^10.4.1",
|
||||
"preact-render-to-string": "^5.1.14",
|
||||
"querystring": "^0.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@next-auth/prisma-legacy-adapter": {
|
||||
"version": "0.0.1-canary.127",
|
||||
"resolved": "https://registry.npmjs.org/@next-auth/prisma-legacy-adapter/-/prisma-legacy-adapter-0.0.1-canary.127.tgz",
|
||||
"integrity": "sha512-Pd2Y8b1ibDywrndbj3751VNKv1mVcg2w0uNIi01EBVkm5pqA1X+VnKWbPeHfh4arLYw93RPCvfLbWBZS7J1gZQ==",
|
||||
"peer": true,
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.14.0"
|
||||
}
|
||||
},
|
||||
"@next-auth/typeorm-legacy-adapter": {
|
||||
"version": "0.0.2-canary.129",
|
||||
"resolved": "https://registry.npmjs.org/@next-auth/typeorm-legacy-adapter/-/typeorm-legacy-adapter-0.0.2-canary.129.tgz",
|
||||
"integrity": "sha512-xEGz3TzBzz+5nXQ6BnC++KGfxTOAgztL32ZRLq47UKz9M0kFBP6pCMJjTszltsBHYUI/Wac2IG2egMTpHtppiQ==",
|
||||
"peer": true,
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.14.0",
|
||||
"require_optional": "^1.0.1",
|
||||
"typeorm": "^0.2.30"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"nice-try": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "next-auth",
|
||||
"version": "0.0.0-semantically-released",
|
||||
"version": "3.29.10",
|
||||
"description": "Authentication for Next.js",
|
||||
"homepage": "https://next-auth.js.org",
|
||||
"repository": "https://github.com/nextauthjs/next-auth.git",
|
||||
@@ -64,8 +64,8 @@
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.14.0",
|
||||
"@next-auth/prisma-legacy-adapter": "0.0.1-canary.127",
|
||||
"@next-auth/typeorm-legacy-adapter": "0.0.2-canary.129",
|
||||
"@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",
|
||||
|
||||
@@ -43,7 +43,7 @@ const sendVerificationRequest = ({
|
||||
},
|
||||
(error) => {
|
||||
if (error) {
|
||||
logger.error("SEND_VERIFICATION_EMAIL_ERROR", email, error)
|
||||
logger.error("SEND_VERIFICATION_EMAIL_ERROR", error)
|
||||
return reject(new Error("SEND_VERIFICATION_EMAIL_ERROR", error))
|
||||
}
|
||||
return resolve()
|
||||
@@ -53,12 +53,11 @@ const sendVerificationRequest = ({
|
||||
}
|
||||
|
||||
// Email HTML body
|
||||
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
|
||||
const html = ({ url, site }) => {
|
||||
// Insert invisible space into domains to prevent the
|
||||
// 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 on their email address to sign in.
|
||||
const escapedEmail = `${email.replace(/\./g, "​.")}`
|
||||
// like they are supposed to click it to sign in.
|
||||
const escapedSite = `${site.replace(/\./g, "​.")}`
|
||||
|
||||
// Some simple styling options
|
||||
@@ -73,17 +72,12 @@ const html = ({ url, site, email }) => {
|
||||
<body style="background: ${backgroundColor};">
|
||||
<table width="100%" border="0" cellspacing="0" cellpadding="0">
|
||||
<tr>
|
||||
<td align="center" style="padding: 10px 0px 20px 0px; font-size: 22px; font-family: Helvetica, Arial, sans-serif; color: ${textColor};">
|
||||
<strong>${escapedSite}</strong>
|
||||
<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>
|
||||
</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">
|
||||
|
||||
20
src/providers/freshbooks.js
Normal file
20
src/providers/freshbooks.js
Normal file
@@ -0,0 +1,20 @@
|
||||
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
|
||||
};
|
||||
}
|
||||
19
src/providers/onelogin.js
Normal file
19
src/providers/onelogin.js
Normal file
@@ -0,0 +1,19 @@
|
||||
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,
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ export default function Yandex(options) {
|
||||
name: "Yandex",
|
||||
type: "oauth",
|
||||
version: "2.0",
|
||||
scope: "login:email login:info",
|
||||
scope: "login:email login:info login:avatar",
|
||||
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: null,
|
||||
image: profile.is_avatar_empty ? null : `https://avatars.yandex.net/get-yapic/${profile.default_avatar_id}/islands-200`,
|
||||
}
|
||||
},
|
||||
...options,
|
||||
|
||||
@@ -21,6 +21,16 @@ 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
|
||||
@@ -71,6 +81,23 @@ 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({
|
||||
@@ -280,7 +307,9 @@ async function NextAuthHandler(req, res, userOptions) {
|
||||
}
|
||||
return res
|
||||
.status(400)
|
||||
.end(`Error: HTTP ${req.method} is not supported for ${req.url}`)
|
||||
.end(
|
||||
`Error: This action with HTTP ${req.method} is not supported by NextAuth.js`
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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,46 +134,47 @@ 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: {
|
||||
sameSite: 'lax',
|
||||
path: '/',
|
||||
secure: useSecureCookies
|
||||
}
|
||||
httpOnly: true,
|
||||
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,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import * as cookie from './cookie'
|
||||
|
||||
/**
|
||||
* Ensure CSRF Token cookie is set for any subsequent requests.
|
||||
* Used as part of the strateigy for mitigation for CSRF tokens.
|
||||
* Used as part of the strategy 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
|
||||
|
||||
@@ -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,10 +28,9 @@ 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(baseUrl)) {
|
||||
return url
|
||||
}
|
||||
export async function redirect(url, baseUrl) {
|
||||
if (url.startsWith("/")) return `${baseUrl}${url}`
|
||||
else if (new URL(url).origin === baseUrl) return url
|
||||
return baseUrl
|
||||
}
|
||||
|
||||
@@ -43,7 +42,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
|
||||
}
|
||||
|
||||
@@ -59,6 +58,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
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ export default async function oAuthCallback(req) {
|
||||
provider.id,
|
||||
code
|
||||
)
|
||||
logger.debug("OAUTH_CALLBACK_HANDLER_ERROR", req.body)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
@@ -62,7 +63,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, code)
|
||||
logger.error("OAUTH_GET_ACCESS_TOKEN_ERROR", error, provider.id)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
@@ -74,7 +75,11 @@ 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,
|
||||
@@ -143,11 +148,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.
|
||||
//
|
||||
// Unfortuately, we can't tell which - at least not in a way that works for
|
||||
// Unfortunately, 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, profileData)
|
||||
logger.error("OAUTH_PARSE_PROFILE_ERROR", exception)
|
||||
return {
|
||||
profile: null,
|
||||
account: null,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { OAuth, OAuth2 } from 'oauth'
|
||||
import querystring from 'querystring'
|
||||
import logger from '../../../lib/logger'
|
||||
import { sign as jwtSign } from 'jsonwebtoken'
|
||||
import { OAuth, OAuth2 } from "oauth"
|
||||
import querystring from "querystring"
|
||||
import logger from "../../../lib/logger"
|
||||
import { sign as jwtSign } from "jsonwebtoken"
|
||||
|
||||
/**
|
||||
* @TODO Refactor to remove dependancy on 'oauth' package
|
||||
@@ -9,8 +9,8 @@ import { sign as jwtSign } from 'jsonwebtoken'
|
||||
* would be easier to maintain if all the code was native to next-auth.
|
||||
* @param {import("types/providers").OAuthConfig} provider
|
||||
*/
|
||||
export default function oAuthClient (provider) {
|
||||
if (provider.version?.startsWith('2.')) {
|
||||
export default function oAuthClient(provider) {
|
||||
if (provider.version?.startsWith("2.")) {
|
||||
// Handle OAuth v2.x
|
||||
const authorizationUrl = new URL(provider.authorizationUrl)
|
||||
const basePath = authorizationUrl.origin
|
||||
@@ -34,9 +34,9 @@ export default function oAuthClient (provider) {
|
||||
provider.accessTokenUrl,
|
||||
provider.clientId,
|
||||
provider.clientSecret,
|
||||
provider.version || '1.0',
|
||||
provider.version || "1.0",
|
||||
provider.callbackUrl,
|
||||
provider.encoding || 'HMAC-SHA1'
|
||||
provider.encoding || "HMAC-SHA1"
|
||||
)
|
||||
|
||||
// Promisify get() and getOAuth2AccessToken() for OAuth1
|
||||
@@ -51,40 +51,48 @@ export default function oAuthClient (provider) {
|
||||
})
|
||||
})
|
||||
}
|
||||
const originalGetOAuth1AccessToken = oauth1Client.getOAuthAccessToken.bind(oauth1Client)
|
||||
const originalGetOAuth1AccessToken =
|
||||
oauth1Client.getOAuthAccessToken.bind(oauth1Client)
|
||||
oauth1Client.getOAuthAccessToken = (...args) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
// eslint-disable-next-line camelcase
|
||||
originalGetOAuth1AccessToken(...args, (error, oauth_token, oauth_token_secret, params) => {
|
||||
if (error) {
|
||||
return reject(error)
|
||||
originalGetOAuth1AccessToken(
|
||||
...args,
|
||||
(error, oauth_token, oauth_token_secret, params) => {
|
||||
if (error) {
|
||||
return reject(error)
|
||||
}
|
||||
|
||||
resolve({
|
||||
// TODO: Remove, this is only kept for backward compativility
|
||||
// These are not in the OAuth 1.x spec
|
||||
accessToken: oauth_token,
|
||||
refreshToken: oauth_token_secret,
|
||||
results: params,
|
||||
|
||||
oauth_token,
|
||||
oauth_token_secret,
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
resolve({
|
||||
// TODO: Remove, this is only kept for backward compativility
|
||||
// These are not in the OAuth 1.x spec
|
||||
accessToken: oauth_token,
|
||||
refreshToken: oauth_token_secret,
|
||||
results: params,
|
||||
|
||||
oauth_token,
|
||||
oauth_token_secret,
|
||||
params
|
||||
})
|
||||
})
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
const originalGetOAuthRequestToken = oauth1Client.getOAuthRequestToken.bind(oauth1Client)
|
||||
const originalGetOAuthRequestToken =
|
||||
oauth1Client.getOAuthRequestToken.bind(oauth1Client)
|
||||
oauth1Client.getOAuthRequestToken = (params = {}) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
// eslint-disable-next-line camelcase
|
||||
originalGetOAuthRequestToken(params, (error, oauth_token, oauth_token_secret, params) => {
|
||||
if (error) {
|
||||
return reject(error)
|
||||
originalGetOAuthRequestToken(
|
||||
params,
|
||||
(error, oauth_token, oauth_token_secret, params) => {
|
||||
if (error) {
|
||||
return reject(error)
|
||||
}
|
||||
resolve({ oauth_token, oauth_token_secret, params })
|
||||
}
|
||||
resolve({ oauth_token, oauth_token_secret, params })
|
||||
})
|
||||
)
|
||||
})
|
||||
}
|
||||
return oauth1Client
|
||||
@@ -104,103 +112,112 @@ export default function oAuthClient (provider) {
|
||||
* @param {import("types/providers").OAuthConfig} provider
|
||||
* @param {string | undefined} codeVerifier
|
||||
*/
|
||||
async function getOAuth2AccessToken (code, provider, codeVerifier) {
|
||||
async function getOAuth2AccessToken(code, provider, codeVerifier) {
|
||||
const url = provider.accessTokenUrl
|
||||
const params = { ...provider.params }
|
||||
const headers = { ...provider.headers }
|
||||
const codeParam = (params.grant_type === 'refresh_token') ? 'refresh_token' : 'code'
|
||||
const codeParam =
|
||||
params.grant_type === "refresh_token" ? "refresh_token" : "code"
|
||||
|
||||
if (!params[codeParam]) { params[codeParam] = code }
|
||||
if (!params[codeParam]) {
|
||||
params[codeParam] = code
|
||||
}
|
||||
|
||||
if (!params.client_id) { params.client_id = provider.clientId }
|
||||
if (!params.client_id) {
|
||||
params.client_id = provider.clientId
|
||||
}
|
||||
|
||||
// For Apple the client secret must be generated on-the-fly.
|
||||
// Using the properties in clientSecret to create a JWT.
|
||||
if (provider.id === 'apple' && typeof provider.clientSecret === 'object') {
|
||||
if (provider.id === "apple" && typeof provider.clientSecret === "object") {
|
||||
const { keyId, teamId, privateKey } = provider.clientSecret
|
||||
const clientSecret = jwtSign({
|
||||
iss: teamId,
|
||||
iat: Math.floor(Date.now() / 1000),
|
||||
exp: Math.floor(Date.now() / 1000) + (86400 * 180), // 6 months
|
||||
aud: 'https://appleid.apple.com',
|
||||
sub: provider.clientId
|
||||
},
|
||||
// Automatically convert \\n into \n if found in private key. If the key
|
||||
// is passed in an environment variable \n can get escaped as \\n
|
||||
privateKey.replace(/\\n/g, '\n'),
|
||||
{ algorithm: 'ES256', keyid: keyId }
|
||||
const clientSecret = jwtSign(
|
||||
{
|
||||
iss: teamId,
|
||||
iat: Math.floor(Date.now() / 1000),
|
||||
exp: Math.floor(Date.now() / 1000) + 86400 * 180, // 6 months
|
||||
aud: "https://appleid.apple.com",
|
||||
sub: provider.clientId,
|
||||
},
|
||||
// Automatically convert \\n into \n if found in private key. If the key
|
||||
// is passed in an environment variable \n can get escaped as \\n
|
||||
privateKey.replace(/\\n/g, "\n"),
|
||||
{ algorithm: "ES256", keyid: keyId }
|
||||
)
|
||||
params.client_secret = clientSecret
|
||||
} else {
|
||||
params.client_secret = provider.clientSecret
|
||||
}
|
||||
|
||||
if (!params.redirect_uri) { params.redirect_uri = provider.callbackUrl }
|
||||
|
||||
if (!headers['Content-Type']) { headers['Content-Type'] = 'application/x-www-form-urlencoded' }
|
||||
// Added as a fix to accomodate change in Twitch OAuth API
|
||||
if (!headers['Client-ID']) { headers['Client-ID'] = provider.clientId }
|
||||
// Added as a fix for Reddit Authentication
|
||||
if (provider.id === 'reddit') {
|
||||
headers.Authorization = 'Basic ' + Buffer.from((provider.clientId + ':' + provider.clientSecret)).toString('base64')
|
||||
if (!params.redirect_uri) {
|
||||
params.redirect_uri = provider.callbackUrl
|
||||
}
|
||||
|
||||
if (provider.id === 'identity-server4' && !headers.Authorization) {
|
||||
if (!headers["Content-Type"]) {
|
||||
headers["Content-Type"] = "application/x-www-form-urlencoded"
|
||||
}
|
||||
// Added as a fix to accomodate change in Twitch OAuth API
|
||||
if (!headers["Client-ID"]) {
|
||||
headers["Client-ID"] = provider.clientId
|
||||
}
|
||||
// Added as a fix for Reddit Authentication
|
||||
if (provider.id === "reddit") {
|
||||
headers.Authorization =
|
||||
"Basic " +
|
||||
Buffer.from(provider.clientId + ":" + provider.clientSecret).toString(
|
||||
"base64"
|
||||
)
|
||||
}
|
||||
|
||||
if (provider.id === "identity-server4" && !headers.Authorization) {
|
||||
headers.Authorization = `Bearer ${code}`
|
||||
}
|
||||
|
||||
if (provider.protection.includes('pkce')) {
|
||||
if (provider.protection.includes("pkce")) {
|
||||
params.code_verifier = codeVerifier
|
||||
}
|
||||
|
||||
const postData = querystring.stringify(params)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this._request(
|
||||
'POST',
|
||||
url,
|
||||
headers,
|
||||
postData,
|
||||
null,
|
||||
(error, data, response) => {
|
||||
if (error) {
|
||||
logger.error('OAUTH_GET_ACCESS_TOKEN_ERROR', error, data, response)
|
||||
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) {
|
||||
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) {
|
||||
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
|
||||
})
|
||||
accessToken = raw.authed_user.access_token
|
||||
} else {
|
||||
accessToken = raw.access_token
|
||||
}
|
||||
)
|
||||
|
||||
resolve({
|
||||
accessToken,
|
||||
accessTokenExpires: null,
|
||||
refreshToken: raw.refresh_token,
|
||||
idToken: raw.id_token,
|
||||
...raw,
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -213,60 +230,69 @@ async function getOAuth2AccessToken (code, provider, codeVerifier) {
|
||||
* @param {string} accessToken
|
||||
* @param {any} results
|
||||
*/
|
||||
async function getOAuth2 (provider, accessToken, results) {
|
||||
async function getOAuth2(provider, accessToken, results) {
|
||||
let url = provider.profileUrl
|
||||
let httpMethod = 'GET'
|
||||
let httpMethod = "GET"
|
||||
const headers = { ...provider.headers }
|
||||
|
||||
if (this._useAuthorizationHeaderForGET) {
|
||||
headers.Authorization = this.buildAuthHeader(accessToken)
|
||||
|
||||
// Mail.ru & vk.com require 'access_token' as URL request parameter
|
||||
if (['mailru', 'vk'].includes(provider.id)) {
|
||||
if (["mailru", "vk"].includes(provider.id)) {
|
||||
const safeAccessTokenURL = new URL(url)
|
||||
safeAccessTokenURL.searchParams.append('access_token', accessToken)
|
||||
safeAccessTokenURL.searchParams.append("access_token", accessToken)
|
||||
url = safeAccessTokenURL.href
|
||||
}
|
||||
|
||||
// This line is required for Twitch
|
||||
if (provider.id === 'twitch') {
|
||||
headers['Client-ID'] = provider.clientId
|
||||
if (provider.id === "twitch") {
|
||||
headers["Client-ID"] = provider.clientId
|
||||
}
|
||||
accessToken = null
|
||||
}
|
||||
|
||||
if (provider.id === 'bungie') {
|
||||
if (provider.id === "bungie") {
|
||||
url = prepareProfileUrl({ provider, url, results })
|
||||
}
|
||||
|
||||
/** Dropbox requires POST instead of GET
|
||||
* Read more: https://www.dropbox.com/developers/reference/auth-types#user
|
||||
*/
|
||||
if (provider.id === 'dropbox') {
|
||||
httpMethod = 'POST'
|
||||
if (provider.id === "dropbox") {
|
||||
httpMethod = "POST"
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this._request(httpMethod, url, headers, null, accessToken, (error, profileData) => {
|
||||
if (error) {
|
||||
return reject(error)
|
||||
this._request(
|
||||
httpMethod,
|
||||
url,
|
||||
headers,
|
||||
null,
|
||||
accessToken,
|
||||
(error, profileData) => {
|
||||
if (error) {
|
||||
return reject(error)
|
||||
}
|
||||
resolve(profileData)
|
||||
}
|
||||
resolve(profileData)
|
||||
})
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/** Bungie needs special handling */
|
||||
function prepareProfileUrl ({ provider, url, results }) {
|
||||
function prepareProfileUrl({ provider, url, results }) {
|
||||
if (!results.membership_id) {
|
||||
// internal error
|
||||
// @TODO: handle better
|
||||
throw new Error('Expected membership_id to be passed.')
|
||||
throw new Error("Expected membership_id to be passed.")
|
||||
}
|
||||
|
||||
if (!provider.headers?.['X-API-Key']) {
|
||||
throw new Error('The Bungie provider requires the X-API-Key option to be present in "headers".')
|
||||
if (!provider.headers?.["X-API-Key"]) {
|
||||
throw new Error(
|
||||
'The Bungie provider requires the X-API-Key option to be present in "headers".'
|
||||
)
|
||||
}
|
||||
|
||||
return url.replace('{membershipId}', results.membership_id)
|
||||
return url.replace("{membershipId}", results.membership_id)
|
||||
}
|
||||
|
||||
@@ -8,14 +8,8 @@ import adapterErrorHandler from "../../adapters/error-handler"
|
||||
* @param {import("types/internals").NextAuthResponse} res
|
||||
*/
|
||||
export default async function signin(req, res) {
|
||||
const {
|
||||
provider,
|
||||
baseUrl,
|
||||
basePath,
|
||||
adapter,
|
||||
callbacks,
|
||||
logger,
|
||||
} = req.options
|
||||
const { provider, baseUrl, basePath, adapter, callbacks, logger } =
|
||||
req.options
|
||||
|
||||
if (!provider.type) {
|
||||
return res.status(500).end(`Error: Type not specified for ${provider.name}`)
|
||||
@@ -44,7 +38,19 @@ export default async function signin(req, res) {
|
||||
// according to RFC 2821, but in practice this causes more problems than
|
||||
// it solves. We treat email addresses as all lower case. If anyone
|
||||
// complains about this we can make strict RFC 2821 compliance an option.
|
||||
const email = req.body.email?.toLowerCase() ?? null
|
||||
let email = req.body.email?.toLowerCase() ?? null
|
||||
|
||||
if (!email) {
|
||||
return res.redirect(`${baseUrl}${basePath}/error?error=EmailSignin`)
|
||||
}
|
||||
|
||||
// Get the first two elements only,
|
||||
// separated by `@` from user input.
|
||||
let [local, domain] = email.split("@")
|
||||
// The part before "@" can contain a ","
|
||||
// but we remove it on the domain part
|
||||
domain = domain.split(",")[0]
|
||||
email = `${local}@${domain}`
|
||||
|
||||
// If is an existing user return a user object (otherwise use placeholder)
|
||||
const profile = (await getUserByEmail(email)) || { email }
|
||||
|
||||
4
types/providers.d.ts
vendored
4
types/providers.d.ts
vendored
@@ -27,7 +27,7 @@ export interface OAuthConfig<P extends Record<string, unknown> = Profile>
|
||||
headers?: Record<string, any>
|
||||
type: "oauth"
|
||||
version: string
|
||||
scope: string
|
||||
scope: string | string[]
|
||||
params: { grant_type: string }
|
||||
accessTokenUrl: string
|
||||
requestTokenUrl?: string
|
||||
@@ -72,6 +72,7 @@ export type OAuthProviderType =
|
||||
| "FACEIT"
|
||||
| "FortyTwo"
|
||||
| "Foursquare"
|
||||
| "Freshbooks"
|
||||
| "FusionAuth"
|
||||
| "GitHub"
|
||||
| "GitLab"
|
||||
@@ -87,6 +88,7 @@ export type OAuthProviderType =
|
||||
| "Naver"
|
||||
| "Netlify"
|
||||
| "Okta"
|
||||
| "OneLogin"
|
||||
| "Osso"
|
||||
| "Reddit"
|
||||
| "Salesforce"
|
||||
|
||||
@@ -33,7 +33,7 @@ Providers.Credentials({
|
||||
type: "password",
|
||||
},
|
||||
},
|
||||
authorize: async ({username, password}) => {
|
||||
authorize: async ({ username, password }) => {
|
||||
const user = {
|
||||
/* fetched user */
|
||||
}
|
||||
@@ -152,6 +152,13 @@ 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",
|
||||
@@ -257,3 +264,9 @@ Providers.Zoho({
|
||||
clientId: "foo123",
|
||||
clientSecret: "bar123",
|
||||
})
|
||||
|
||||
// $ExpectType OAuthConfig<Profile>
|
||||
Providers.Freshbooks({
|
||||
clientId: "foo123",
|
||||
clientSecret: "bar123",
|
||||
})
|
||||
|
||||
@@ -49,6 +49,8 @@ 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" })
|
||||
@@ -76,7 +78,7 @@ CreateIndex({
|
||||
terms: [{ field: ["data", "email"] }],
|
||||
})
|
||||
CreateIndex({
|
||||
name: "verification_request_by_token",
|
||||
name: "verification_request_by_token_and_identifier",
|
||||
source: Collection("verification_requests"),
|
||||
unique: true,
|
||||
terms: [{ field: ["data", "token"] }, { field: ["data", "identifier"] }],
|
||||
|
||||
@@ -15,7 +15,7 @@ This is the Firebase Adapter for [`next-auth`](https://next-auth.js.org). This p
|
||||
npm install next-auth @next-auth/firebase-adapter
|
||||
```
|
||||
|
||||
2. Add this adapter to your `pages/api/[...nextauth].js` next-auth configuration object.
|
||||
2. Add this adapter to your `pages/api/auth/[...nextauth].js` next-auth configuration object.
|
||||
|
||||
```javascript title="pages/api/auth/[...nextauth].js"
|
||||
import NextAuth from "next-auth"
|
||||
|
||||
@@ -70,7 +70,7 @@ callbacks: {
|
||||
|
||||
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 `authorization` 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 `authorize` 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.
|
||||
@@ -188,6 +188,8 @@ 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.
|
||||
|
||||
@@ -123,27 +123,34 @@ 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.
|
||||
// secret: 'INp8IvdIyeMcoGAgFGoA61DdBglwwSqnXJZkgz8PSnw',
|
||||
// You can generate a secret be using `openssl rand -base64 64`
|
||||
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,
|
||||
// encryptionKey: "",
|
||||
// decryptionKey = encryptionKey,
|
||||
// decryptionOptions = {
|
||||
// algorithms: ['A256GCM']
|
||||
// },
|
||||
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']
|
||||
},
|
||||
// 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 }) {},
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -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 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)
|
||||
• 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)
|
||||
|
||||
That's it! 🎉 Others will be able to discover this provider much more easily now!
|
||||
|
||||
|
||||
@@ -23,13 +23,11 @@ You can use also NextAuth.js with any database using a custom database adapter,
|
||||
|
||||
### What authentication services does NextAuth.js support?
|
||||
|
||||
|
||||
<p>NextAuth.js includes built-in support for signing in with
|
||||
{Object.values(require("../providers.json")).sort().join(", ")}.
|
||||
(See also: <a href="/configuration/providers">Providers</a>)
|
||||
</p>
|
||||
|
||||
|
||||
NextAuth.js also supports email for passwordless sign in, which is useful for account recovery or for people who are not able to use an account with the configured OAuth services (e.g. due to service outage, account suspension or otherwise becoming locked out of an account).
|
||||
|
||||
You can also use a custom based provider to support signing in with a username and password stored in an external database and/or using two factor authentication.
|
||||
@@ -58,7 +56,6 @@ NextAuth.js is designed as a secure, confidential client and implements a server
|
||||
|
||||
It is not intended to be used in native applications on desktop or mobile applications, which typically implement public clients (e.g. with client / secrets embedded in the application).
|
||||
|
||||
|
||||
### Is NextAuth.js supporting TypeScript?
|
||||
|
||||
Yes! Check out the [TypeScript docs](/getting-started/typescript)
|
||||
@@ -83,9 +80,9 @@ If you are using a database with NextAuth.js, you can still explicitly enable JS
|
||||
|
||||
### Should I use a database?
|
||||
|
||||
* Using NextAuth.js without a database works well for internal tools - where you need to control who is able to sign in, but when you do not need to create user accounts for them in your application.
|
||||
- Using NextAuth.js without a database works well for internal tools - where you need to control who is able to sign in, but when you do not need to create user accounts for them in your application.
|
||||
|
||||
* Using NextAuth.js with a database is usually a better approach for a consumer facing application where you need to persist accounts (e.g. for billing, to contact customers, etc).
|
||||
- Using NextAuth.js with a database is usually a better approach for a consumer facing application where you need to persist accounts (e.g. for billing, to contact customers, etc).
|
||||
|
||||
### What database should I use?
|
||||
|
||||
@@ -93,16 +90,15 @@ Managed database solutions for MySQL, Postgres and MongoDB (and compatible datab
|
||||
|
||||
If you are deploying directly to a particular cloud platform you may also want to consider serverless database offerings they have (e.g. [Amazon Aurora Serverless on AWS](https://aws.amazon.com/rds/aurora/serverless/)).
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Security
|
||||
## Security
|
||||
|
||||
### I think I've found a security problem, what should I do?
|
||||
|
||||
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 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 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.
|
||||
|
||||
### What is the disclosure policy for NextAuth.js?
|
||||
|
||||
@@ -165,14 +161,14 @@ Ultimately if your request is not accepted or is not actively in development, yo
|
||||
|
||||
---
|
||||
|
||||
## JSON Web Tokens
|
||||
## JSON Web Tokens
|
||||
|
||||
### Does NextAuth.js use JSON Web Tokens?
|
||||
|
||||
NextAuth.js supports both database session tokens and JWT session tokens.
|
||||
|
||||
* If a database is specified, database session tokens will be used by default.
|
||||
* If no database is specified, JWT session tokens will be used by default.
|
||||
- If a database is specified, database session tokens will be used by default.
|
||||
- If no database is specified, JWT session tokens will be used by default.
|
||||
|
||||
You can also choose to use JSON Web Tokens as session tokens with using a database, by explicitly setting the `session: { jwt: true }` option.
|
||||
|
||||
@@ -180,33 +176,33 @@ You can also choose to use JSON Web Tokens as session tokens with using a databa
|
||||
|
||||
JSON Web Tokens can be used for session tokens, but are also used for lots of other things, such as sending signed objects between services in authentication flows.
|
||||
|
||||
* Advantages of using a JWT as a session token include that they do not require a database to store sessions, this can be faster and cheaper to run and easier to scale.
|
||||
- Advantages of using a JWT as a session token include that they do not require a database to store sessions, this can be faster and cheaper to run and easier to scale.
|
||||
|
||||
* JSON Web Tokens in NextAuth.js are secured using cryptographic signing (JWS) by default and it is easy for services and API endpoints to verify tokens without having to contact a database to verify them.
|
||||
- JSON Web Tokens in NextAuth.js are secured using cryptographic signing (JWS) by default and it is easy for services and API endpoints to verify tokens without having to contact a database to verify them.
|
||||
|
||||
* You can enable encryption (JWE) to store include information directly in a JWT session token that you wish to keep secret and use the token to pass information between services / APIs on the same domain.
|
||||
- You can enable encryption (JWE) to store include information directly in a JWT session token that you wish to keep secret and use the token to pass information between services / APIs on the same domain.
|
||||
|
||||
* You can use JWT to securely store information you do not mind the client knowing even without encryption, as the JWT is stored in a server-readable-only-token so data in the JWT is not accessible to third party JavaScript running on your site.
|
||||
- You can use JWT to securely store information you do not mind the client knowing even without encryption, as the JWT is stored in a server-readable-only-token so data in the JWT is not accessible to third party JavaScript running on your site.
|
||||
|
||||
### What are the disadvantages of JSON Web Tokens?
|
||||
|
||||
* You cannot as easily expire a JSON Web Token - doing so requires maintaining a server side blocklist of invalid tokens (at least until they expire) and checking every token against the list every time a token is presented.
|
||||
- You cannot as easily expire a JSON Web Token - doing so requires maintaining a server side blocklist of invalid tokens (at least until they expire) and checking every token against the list every time a token is presented.
|
||||
|
||||
Shorter session expiry times are used when using JSON Web Tokens as session tokens to allow sessions to be invalidated sooner and simplify this problem.
|
||||
|
||||
NextAuth.js client includes advanced features to mitigate the downsides of using shorter session expiry times on the user experience, including automatic session token rotation, optionally sending keep alive messages to prevent short lived sessions from expiring if there is an window or tab open, background re-validation, and automatic tab/window syncing that keeps sessions in sync across windows any time session state changes or a window or tab gains or loses focus.
|
||||
|
||||
* As with database session tokens, JSON Web Tokens are limited in the amount of data you can store in them. There is typically a limit of around 4096 bytes per cookie, though the exact limit varies between browsers, proxies and hosting services. If you want to support most browsers, then do not exceed 4096 bytes per cookie. If you want to save more data, you will need to persist your sessions in a database (Source: [browsercookielimits.iain.guru](http://browsercookielimits.iain.guru/))
|
||||
- As with database session tokens, JSON Web Tokens are limited in the amount of data you can store in them. There is typically a limit of around 4096 bytes per cookie, though the exact limit varies between browsers, proxies and hosting services. If you want to support most browsers, then do not exceed 4096 bytes per cookie. If you want to save more data, you will need to persist your sessions in a database (Source: [browsercookielimits.iain.guru](http://browsercookielimits.iain.guru/))
|
||||
|
||||
The more data you try to store in a token and the more other cookies you set, the closer you will come to this limit. If you wish to store more than ~4 KB of data you're probably at the point where you need to store a unique ID in the token and persist the data elsewhere (e.g. in a server-side key/value store).
|
||||
|
||||
* Data stored in an encrypted JSON Web Token (JWE) may be compromised at some point.
|
||||
- Data stored in an encrypted JSON Web Token (JWE) may be compromised at some point.
|
||||
|
||||
Even if appropriately configured, information stored in an encrypted JWT should not be assumed to be impossible to decrypt at some point - e.g. due to the discovery of a defect or advances in technology.
|
||||
|
||||
Avoid storing any data in a token that might be problematic if it were to be decrypted in the future.
|
||||
|
||||
* If you do not explicitly specify a secret for for NextAuth.js, existing sessions will be invalidated any time your NextAuth.js configuration changes, as NextAuth.js will default to an auto-generated secret.
|
||||
- If you do not explicitly specify a secret for 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.
|
||||
|
||||
@@ -214,11 +210,11 @@ JSON Web Tokens can be used for session tokens, but are also used for lots of ot
|
||||
|
||||
By default tokens are signed (JWS) but not encrypted (JWE), as encryption adds additional overhead and reduces the amount of space available to store data (total cookie size for a domain is limited to 4KB).
|
||||
|
||||
* JSON Web Tokens in NextAuth.js use JWS and are signed using HS512 with an auto-generated key.
|
||||
- JSON Web Tokens in NextAuth.js use JWS and are signed using HS512 with an auto-generated key.
|
||||
|
||||
* If encryption is enabled by setting `jwt: { encryption: true }` option then the JWT will _also_ use JWE to encrypt the token, using A256GCM with an auto-generated key.
|
||||
- If encryption is enabled by setting `jwt: { encryption: true }` option then the JWT will _also_ use JWE to encrypt the token, using A256GCM with an auto-generated key.
|
||||
|
||||
You can specify other valid algorithms - [as specified in RFC 7518](https://tools.ietf.org/html/rfc7517) - with either a secret (for symmetric encryption) or a public/private key pair (for a symmetric encryption).
|
||||
You can specify other valid algorithms - [as specified in RFC 7518](https://tools.ietf.org/html/rfc7517) - with either a secret (for symmetric encryption) or a public/private key pair (for a symmetric encryption).
|
||||
|
||||
NextAuth.js will generate keys for you, but this will generate a warning at start up.
|
||||
|
||||
@@ -228,14 +224,14 @@ Using explicit public/private keys for signing is strongly recommended.
|
||||
|
||||
NextAuth.js includes a largely complete implementation of JSON Object Signing and Encryption (JOSE):
|
||||
|
||||
* [RFC 7515 - JSON Web Signature (JWS)](https://tools.ietf.org/html/rfc7515)
|
||||
* [RFC 7516 - JSON Web Encryption (JWE)](https://tools.ietf.org/html/rfc7516)
|
||||
* [RFC 7517 - JSON Web Key (JWK)](https://tools.ietf.org/html/rfc7517)
|
||||
* [RFC 7518 - JSON Web Algorithms (JWA)](https://tools.ietf.org/html/rfc7518)
|
||||
* [RFC 7519 - JSON Web Token (JWT)](https://tools.ietf.org/html/rfc7519)
|
||||
- [RFC 7515 - JSON Web Signature (JWS)](https://tools.ietf.org/html/rfc7515)
|
||||
- [RFC 7516 - JSON Web Encryption (JWE)](https://tools.ietf.org/html/rfc7516)
|
||||
- [RFC 7517 - JSON Web Key (JWK)](https://tools.ietf.org/html/rfc7517)
|
||||
- [RFC 7518 - JSON Web Algorithms (JWA)](https://tools.ietf.org/html/rfc7518)
|
||||
- [RFC 7519 - JSON Web Token (JWT)](https://tools.ietf.org/html/rfc7519)
|
||||
|
||||
This incorporates support for:
|
||||
|
||||
* [RFC 7638 - JSON Web Key Thumbprint](https://tools.ietf.org/html/rfc7638)
|
||||
* [RFC 7787 - JSON JWS Unencoded Payload Option](https://tools.ietf.org/html/rfc7797)
|
||||
* [RFC 8037 - CFRG Elliptic Curve ECDH and Signatures](https://tools.ietf.org/html/rfc8037)
|
||||
- [RFC 7638 - JSON Web Key Thumbprint](https://tools.ietf.org/html/rfc7638)
|
||||
- [RFC 7787 - JSON JWS Unencoded Payload Option](https://tools.ietf.org/html/rfc7797)
|
||||
- [RFC 8037 - CFRG Elliptic Curve ECDH and Signatures](https://tools.ietf.org/html/rfc8037)
|
||||
|
||||
@@ -274,7 +274,7 @@ The following parameters are always overridden server-side: `redirect_uri`, `sta
|
||||
- Client Side: **Yes**
|
||||
- Server Side: No
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
It reloads the page in the browser when complete.
|
||||
|
||||
@@ -346,21 +346,22 @@ 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 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 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>
|
||||
)
|
||||
|
||||
@@ -5,11 +5,11 @@ title: Azure Active Directory B2C
|
||||
|
||||
## Documentation
|
||||
|
||||
https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow
|
||||
https://docs.microsoft.com/azure/active-directory/develop/v2-oauth2-auth-code-flow
|
||||
|
||||
## Configuration
|
||||
|
||||
https://docs.microsoft.com/en-us/azure/active-directory-b2c/tutorial-create-tenant
|
||||
https://docs.microsoft.com/azure/active-directory-b2c/tutorial-create-tenant
|
||||
|
||||
## Options
|
||||
|
||||
|
||||
@@ -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; 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; border-radius: 5px; padding: 10px 20px; border: 1px solid ${buttonBorderColor}; display: inline-block; font-weight: bold;">Sign in</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
|
||||
33
www/docs/providers/freshbooks.md
Normal file
33
www/docs/providers/freshbooks.md
Normal file
@@ -0,0 +1,33 @@
|
||||
---
|
||||
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,
|
||||
})
|
||||
]
|
||||
...
|
||||
```
|
||||
@@ -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/linked-in.js)
|
||||
- [LinkedIn Provider options](https://github.com/nextauthjs/next-auth/blob/main/src/providers/linkedin.js)
|
||||
|
||||
You can override any of the options to suit your own use case.
|
||||
|
||||
|
||||
31
www/docs/providers/onelogin.md
Normal file
31
www/docs/providers/onelogin.md
Normal file
@@ -0,0 +1,31 @@
|
||||
---
|
||||
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
|
||||
})
|
||||
]
|
||||
...
|
||||
```
|
||||
@@ -94,3 +94,7 @@ 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.
|
||||
|
||||
@@ -68,10 +68,6 @@ module.exports = {
|
||||
label: "Introduction",
|
||||
to: "/getting-started/introduction",
|
||||
},
|
||||
{
|
||||
label: "Contributors",
|
||||
to: "/contributors",
|
||||
},
|
||||
{
|
||||
label: "Next documentation",
|
||||
to: "https://next-auth-git-next.nextauthjs.vercel.app",
|
||||
@@ -95,8 +91,12 @@ module.exports = {
|
||||
title: "Acknowledgements",
|
||||
items: [
|
||||
{
|
||||
label: "Docusaurus",
|
||||
to: "https://v2.docusaurus.io/",
|
||||
label: "Contributors",
|
||||
to: "/contributors",
|
||||
},
|
||||
{
|
||||
label: "Sponsors",
|
||||
to: "https://opencollective.com/nextauth",
|
||||
},
|
||||
{
|
||||
label: "Images by unDraw",
|
||||
|
||||
Reference in New Issue
Block a user