和:
now.sh
上的简单无密码电子邮件后端我认为这对很多新手会有很大帮助。
建议:使用 Redux 和 JWT 来完成示例
我正在为此做一个例子。 目前有问题让 componentWillReceiveProps 在我的高级组件上触发(我计划检查用户是否通过身份验证,如果没有,则重定向到登录页面)
所以我有 auth 工作游泳。 正如其他地方提到的,它只是客户端,这最终只是成功的一半。
和php一样,Next的原子单位是页面。 最酷的功能之一是它仅在请求时才延迟加载每个页面。 仅使用客户端身份验证但使用服务器呈现,该受保护页面的 js 实际上是由浏览器下载的。 将来当 Next 添加服务器工作流时,您希望能够在服务器上阻止渲染和重定向以完全防止这种情况。 这将需要 cookie、会话和 AFAIK 会话存储,但这只是执行此类混合应用程序的成本。
假设您有一个受 JWT 保护的 API,其中包含两个感兴趣的端点: /token
和/me
。 /token
接受电子邮件/密码凭据并返回签名的 JWT ( id_token
),而/me
返回与 JWT 身份验证用户相关的个人资料信息。 我改编了以下AuthService.js
来自 Auth0 的锁(删除事件发射器,虽然这不是最糟糕的主意)。 它提取了几乎所有的 JWT 令牌处理,因此可以在登录页面和高阶组件中使用(稍后会详细介绍)。
// utils/AuthService.js
export default class AuthService {
constructor(domain) {
this.domain = domain || 'http://localhost:5000'
this.fetch = this.fetch.bind(this)
this.login = this.login.bind(this)
this.getProfile = this.getProfile.bind(this)
}
login(email, password) {
// Get a token
return this.fetch(`${this.domain}/token`, {
method: 'POST',
body: JSON.stringify({
email,
password
})
}).then(res => {
this.setToken(res.id_token)
return this.fetch(`${this.domain}/user`, {
method: 'GET'
})
}).then(res => {
this.setProfile(res)
return Promise.resolve(res)
})
}
loggedIn(){
// Checks if there is a saved token and it's still valid
const token = this.getToken()
return !!token && !isTokenExpired(token) // handwaiving here
}
setProfile(profile){
// Saves profile data to localStorage
localStorage.setItem('profile', JSON.stringify(profile))
}
getProfile(){
// Retrieves the profile data from localStorage
const profile = localStorage.getItem('profile')
return profile ? JSON.parse(localStorage.profile) : {}
}
setToken(idToken){
// Saves user token to localStorage
localStorage.setItem('id_token', idToken)
}
getToken(){
// Retrieves the user token from localStorage
return localStorage.getItem('id_token')
}
logout(){
// Clear user token and profile data from localStorage
localStorage.removeItem('id_token');
localStorage.removeItem('profile');
}
_checkStatus(response) {
// raises an error in case response status is not a success
if (response.status >= 200 && response.status < 300) {
return response
} else {
var error = new Error(response.statusText)
error.response = response
throw error
}
}
fetch(url, options){
// performs api calls sending the required authentication headers
const headers = {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
if (this.loggedIn()){
headers['Authorization'] = 'Bearer ' + this.getToken()
}
return fetch(url, {
headers,
...options
})
.then(this._checkStatus)
.then(response => response.json())
}
}
接下来是使保护页面更简单的 HOC。 为了防止不必要的敏感信息闪现,页面将在第一次渲染时服务器渲染Loading...
,而 react 启动/从 localStorage 读取令牌。 这意味着受保护的页面不会进行SEO,目前这可能还可以,但绝对不是最佳的。
// utils/withAuth.js - a HOC for protected pages
import React, {Component} from 'react'
import AuthService from './auth'
export default function withAuth(AuthComponent) {
const Auth = new AuthService('http://localhost:5000')
return class Authenticated extends Component {
constructor(props) {
super(props)
this.state = {
isLoading: true
};
}
componentDidMount () {
if (!Auth.loggedIn()) {
this.props.url.replaceTo('/')
}
this.setState({ isLoading: false })
}
render() {
return (
<div>
{this.state.isLoading ? (
<div>LOADING....</div>
) : (
<AuthComponent {...this.props} auth={Auth} />
)}
</div>
)
}
}
}
// ./pages/dashboard.js
// example of a protected page
import React from 'react'
import withAuth from '../utils/withAuth'
class Dashboard extends Component {
render() {
const user = this.props.auth.getProfile()
return (
<div>Current user: {user.email}</div>
)
}
}
export default withAuth(Dashboard)
现在登录页面不能使用 HOC,因为登录需要公开。 所以它只是直接创建了一个 AuthService 的实例。 您也可以为注册页面做类似的事情。
// ./pages/login.js
import React, {Component} from 'react'
import AuthService from '../utils/AuthService'
const auth = new AuthService('http://localhost:5000')
class Login extends Component {
constructor(props) {
super(props)
this.handleSubmit = this.handleSubmit.bind(this)
}
componentDidMount () {
if (auth.loggedIn()) {
this.props.url.replaceTo('/admin') // redirect if you're already logged in
}
}
handleSubmit (e) {
e.preventDefault()
// yay uncontrolled forms!
auth.login(this.refs.email.value, this.refs.password.value)
.then(res => {
console.log(res)
this.props.url.replaceTo('/admin')
})
.catch(e => console.log(e)) // you would show/hide error messages with component state here
}
render () {
return (
<div>
Login
<form onSubmit={this.handleSubmit} >
<input type="text" ref="email"/>
<input type="password" ref="password"/>
<input type="submit" value="Submit"/>
</form>
</div>
)
}
}
export default Login
受到 Airbnb 的 react-with-styles 的启发,我还开始研究next-with-auth
库,它是一个函数,返回一个用于页面的 HOC。 我还尝试合并AuthService
和这个 HOC。 一种解决方案可能是让这个 HOC 接受一个权限级别的函数作为除了组件之外的参数,比如 redux connect。 无论如何,在我看来,你会像这样使用next-with-auth
:
// ./utils/withAuth.js
import nextAuth from 'next/auth'
import parseScopes from './parseScopes'
const Loading = () => <div>Loading...</div>
export default nextAuth({
url: 'http://localhost:5000',
tokenEndpoint: '/api/token',
profileEndpoint: '/api/me',
getTokenFromResponse: (res) => res.id_token,
getProfileFromResponse: (res) => res,
parseScopes,
})
使用 Redux 完成这一切似乎不必要地复杂,但基本上您可以按照 wiki 示例进行操作,但将 AuthService 移至 Actions(登录和注销)并拥有一个 User Reducer。 但是,您只能在客户端上调用这些操作,因为服务器上没有 localStorage,因此您需要在 Actions 中检查它。 最终,redux store 无论如何都放在window
。 因此,您可以自己将用户缓存在window
上,而不是使用上下文。 如果你不想要 redux,你也可以试试react-broadcast
。
最后,假设next/server
根据 #25 发货。 next-with-auth
可以通过中间件 + HOC 将复杂的 localStorage 与 cookie 的东西从开发人员那里抽象出来。 它还可以处理令牌刷新。
很高兴尝试这个! 感谢您的准系统实现:)
@jaredpalmer我正在做类似的事情。 当组件在服务器端呈现时,您的AuthService
工作的? 服务器需要访问 JWT,但无法从本地存储读取它。
@amccloud没有。 这就是整个问题。 HOC 在受保护的路由上呈现<div>Loading..</div>
并且必须读取令牌并决定是否在componentDidMount
重定向。 为了让它按照您希望的方式工作并呈现服务器端,Next 需要 #25,或者至少能够使用 JWT AFAIK 的值设置 cookie。
我使用 cookie-js 来设置 cookie,但它有点 hack..
问题是:如果您不发送 cookie,您将失去 nextjs 和服务器端在经过身份验证的路由中呈现的所有好处
@jaredpalmer这太棒了! 感谢您的努力。 我将在接下来的几天内尝试完成您的示例(或者如果您愿意,可以帮助您完成)
哟! 我在这里发布了一个带有 nextjs 和 auth0 的示例: https :
它具有主布局的概念以及仅在用户通过身份验证时加载的“安全页面”。
让我知道你的想法🎉
@luisrudge太棒了。 我正在克隆并进行一些更改,但看起来很棒
凉爽的! 你认为它缺少什么? 你在想什么变化?
在 2016 年 11 月 6 日星期日下午 1:12 -0200,“Dan Zajdband” < [email protected] [email protected] > 写道:
@luisr udgehttps://github.com/luisrudge太棒了。 我正在克隆并进行一些更改,但看起来很棒
你收到这个是因为你被提到了。
直接回复本邮件,在Gi tHub上查看阅读https://github.com/notifications/unsubscribe-auth/ AA5cE8NIsvQ_ITjc1gArTFgNXzEda4TSks5q7e5NgaJpZM4KkJmi。
1) 使用standard
进行 linting(因此它与我们接下来构建的所有内容一致)
2) 添加@rauchg 请求的多标签支持
3)css部分可以简化
我给你发个pr :)
多标签支持是什么意思?
在 2016 年 11 月 6 日星期日下午 1:16 -0200,“Dan Zajdband” < [email protected] [email protected] > 写道:
1) 使用标准进行 linting(因此它与我们接下来构建的所有内容一致)
2) 添加@rauchghttps ://github.com/rauchg 请求的多标签支持
3)css部分可以简化
我给你发个pr :)
你收到这个是因为你被提到了。
直接回复本邮件,在Gi tHub上查看阅读https://github.com/notifications/unsubscribe-auth/ AA5cE1A6jq4KZc9_ynukTCI4mU-rdsNaks5q7e81gaJpZM4KkJmi。
您有 2 个打开的选项卡,在 1 个上注销,在另一个上自动注销
啊。 这太酷了!
在 2016 年 11 月 6 日星期日下午 1:21 -0200,“Dan Zajdband” < [email protected] [email protected] > 写道:
您有 2 个打开的选项卡,在 1 个上注销,在其他选项卡上自动注销
你收到这个是因为你被提到了。
直接回复本邮件,在Gi tHub上查看阅读https://github.com/notifications/unsubscribe-auth/ AA5cE9e2DA4_GgNQIVTMp0hx74G-6RmUks5q7fBfgaJpZM4KkJmi。
嗨@luisrudge我给你发了一个 PR 与更改https://github.com/luisrudge/next.js-auth0/pull/2
非常感谢你这样做<3
顺便说一句,这是结果:
@impronunciable @luisrudge 很棒的实现! 如果你想在没有 Auth0 的情况下使用它,看起来你只需要更改 ./utils 目录中的文件,甚至可能只是lock.js
。 我很快就会尝试这个。 顺便说一句,多标签看起来很棒💯
@ugiacoman我已经开始使用 passwordless.net 实现一个小型服务器,如果您想将我的代码作为起点,请告诉我
@impronunciable那太棒了! 我实际上打算用 Twitter Fabric 的 Digits 做一些类似的事情。
@impronuncible我建议不要使用密码 less.net ,您可以只使用本地护照,并在查询字符串中向用户发送带有电子邮件和令牌的链接。
谢谢@impronunciable ❤️
@ugiacoman是的,删除 auth0 依赖项非常容易。 我使用它是因为我不想有一个单独的 api 来处理身份验证
@jaredpalmer据我所知,拥有#25 会很棒,但不会阻塞吗? 我的意思是我们可以访问getInitialProps
的服务器端req
getInitialProps
所以没有什么可以阻止将cookie-parser
应用于它? 服务器端身份验证和会话管理对我来说都是新东西 😬
顺便说一句,考虑到localStorage
不能在服务器端使用,cookies 是服务器端会话的唯一方法吗? 我有一个模糊的记忆,它可能不是最安全的? 但还有其他选择吗?
@sedubois
如果使用得当,cookie 方法可以非常安全。 执行以下操作相当简单:
当您可以直接在服务器上访问身份验证信息时,还有一个非常显着的延迟优势。
我们应该把这个例子移到examples/
我会看看我能想出什么
我通过以下方式将 nextjs 包装在自定义快速服务器中,设法将react-cookie
用于同构 cookie:
const express = require('express')
const next = require('next')
const cookie = require('react-cookie')
const cookieParser = require('cookie-parser')
const app = next({ dev: true, dir: process.cwd() })
const handle = app.getRequestHandler()
app.prepare().then(() => {
const server = express()
server.use(cookieParser()) // <---- this line
server.get('*', (req, res) => {
cookie.plugToRequest(req, res) // <---- this line
return handle(req, res)
})
server.listen(3000, (err) => {
if (err) throw err
console.log('> Ready on http://localhost:3000')
})
})
这允许我从服务器端发出经过身份验证的请求。 这并没有解决原始项目符号中的任何问题,但它解决了客户端和服务器之间共享状态的问题。
来自正在学习很多这些东西的人的 POV。 我认为如果示例不依赖于 auth0 之类的 3rd 方服务,那将是最好的。 对于新手来说,看到一个带有登录/注册表单以及使用 Redux 和 JWT 的更准系统的示例会更有益。
我们计划 _bundle_ 的示例将基于开源 Node.js 服务器 API
我在https://github.com/iaincollins/nextjs-starter上的示例启动项目中添加了一个基于电子邮件的身份验证示例
它有会话支持(后端有 Express Sessions,前端有浏览器 sessionStorage API 来缓存它们),httpOnly cookie,CSRF 投影,使用内置的 SMTP 发送电子邮件,一个易于更改的后端默认为 SQL Lite . 运行它不需要任何配置。
该项目还有布局页面、自定义路线,并包括来自 wiki 的时钟示例。 这不是最奇特的身份验证示例,但可能对那些希望通过易于理解和使用的简单项目轻松入门的人有所帮助。
我同意@iamjacks 的观点,JWT 的例子似乎是个好主意。
我很高兴改进错误处理并添加一些功能,例如用户可以编辑的简单个人资料页面,以及将护照与 Facebook、Google 和 Twitter 的 oAuth 示例集成,如果这对人们有用的话。 如果人们对将会话信息传播/公开给组件的更好方法有好的想法,我非常感兴趣。
这是非常不可思议的@iaincollins。 我们肯定会在 2.0 的发行说明中介绍这一点:)
@rauchg谢谢! :)
我想我应该明确地补充一点,在这个例子中,会话是基于客户端和服务器的——即使用和不使用 JavaScript(以及在没有 sessionStorage 的系统上),并且在两者之间共享相同的会话。
在 Session 组件中实现这一点有点麻烦,它深入研究名称为 req.connection._httpMessage.locals._csrf 的变量以从服务器标头中获取 CSRF 令牌 - 作为传递给 getInitialProps() 中的页面的“req”对象与 Express 中公开的 req 对象有点不同,因为我通常通过 req.locals._csrf 访问它(但是 req.session 在两者中是相同的)。
localStorage 不安全。 什么是使其更安全的最佳方法。 任何人都可以窃取 localStorage 数据并将其重新放入他的浏览器中,并且可以作为受害者用户登录!!
@Chathula那不是真的。 这个论点是错误的。
如果任何人都可以物理访问浏览器,他们就可以做任何事情。
对于 cookie 也是如此。
使用 localStorage 进行身份验证是一种安全,因为我们可能会摆脱基于 cookie 的 sec 问题。
但另一方面,它会影响SSR。
@arunoda我已经使用 Laravel API 和 Next.js 客户端创建了登录。 我将 authUser access_token 存储在 localStorage 中。 然后通过身份验证检查用户是否登录。 但它不安全。 如果有人窃取了 localStorage 数据。 他/她可以使用它。
如果有人窃取了 localStorage 数据。
如何? 基本上他/她应该可以物理访问浏览器。 然后那个人可以做任何事情。
所以,我们不应该担心。
我们不能使用任何加密来使其更安全吗?
@Chathula这离题了。 它与 Next.js 并不完全相关,我们想要了解 Web 内容的正常工作方式。
@Chathula可能是你可以在上面的启动项目中启动一个新线程。
@arunoda哈哈哈!! 谢谢你的信息! :D
@Chathula如果您有特定的顾虑,我很高兴在启动项目的问题中更多地讨论它。
我想纠正任何误解,以免引起不必要的恐慌。
Web 存储 API(即 localStorage 和 sessionStorage)就像没有设置 httpOnly 的 cookie 一样 - 通过同源策略(协议、主机名、端口号)进行限制,“任何人都可以窃取 [it]”是不正确的; 但是是的,如果有人能够通过应用程序中的跨站点脚本漏洞在您的站点上执行任意 JavaScript,那么他们也可以访问商店,因此您不应在其中存储会话标识符。
这就是为什么您会看到会话令牌本身没有存储在 localStorage/sessionStorage 中并且在 JavaScript 中不可读,它仅通过 HTTP Only cookie 传输(这就是会话类使用 XMLHttpRequest() 而不是 fetch() 的原因 -如类文档中所述)。
这意味着即使有人能够利用您应用程序中的跨站点脚本漏洞并在您的 Web 应用程序中执行任意 JavaScript,他们仍然无法读取或导出用户会话令牌。
这可能是值得在文档中进行研究的一个重要区别。
注意:用户数据的额外加密在这里没有帮助,因为应用程序总是需要能够读取数据以便可以呈现(因此您还需要在应用程序中存储描述密钥,这将呈现加密用户数据相当没有实际意义)。
_UPDATE:在过去一两周中,示例被重构为在 sessionStorage 上使用 localStorage,因为 sessionStorage 不在选项卡之间共享,并且以这种方式共享非敏感数据减少了不必要的身份验证检查次数并保持选项卡之间的会话状态一致。_
也许对某些人有用我在试验时制作了这个示例应用程序:
https://github.com/possibilities/next.js-with-auth
由这个玩具后端支持:
https://github.com/possibilities/micro-auth
部署在这里:
https://next-with-auth.now.sh/
后台在这里:
@possibilities 谢谢迈克! 以及公开一个单独的微服务,它展示了一种处理安全页面的好方法,我认为这可能是很好的 pragma 并可能从中获得灵感。 我有一些想法将在 next.js-with-auth 存储库中提出。
在再三考虑之后,我可能不会把我的努力作为一个很好的例子。 我可能会更改为让注册/登录完全提交 web 1.0 样式,以便我们可以在服务器上建立仅 HTTP cookie(通过 XSS 消除对 JWT 的可访问性),然后将用户对象附加到req
而不是比整个令牌。 这留下了 CSRF 漏洞的可能性,但我认为这比 XSS 更容易缓解(不是吗?)。 这样做的一个很好的副作用是客户端应用程序可以在通过誓言服务登录时使用几乎相同的流程。
事后看来,我还可以通过解析Page
HoC 的getInitialProps
的 cookie 来避免“中间件”(因此需要自定义服务器)。
@possibilities由于“秘密”页面只是一个页面并且由 webpack 捆绑,如果我转到 /secret,它不会提供给浏览器吗?
是的,我想应该连接到服务器端重定向。
顺便说一句,我分心回到了我最初使用 github 登录的项目。 流程类似,但更关心安全性(即不在客户端公开任何机密以避免通过 XSS 公开 oauth 令牌)。 它绑定在一个适当的应用程序中,但如果有任何兴趣,我可以将其分解为通常对 oauth 流程有用的东西。
@possibilities我认为这将是一个很棒的帮助,如果可以有一个带有身份验证示例的最小(但合适)的 PR 🙂 我正在我的应用程序中进行身份验证(https://github.com/relatenow/相关),但目前只是客户端(localStorage)。
@sedubois我有一个使用 passwordless.net https://github.com/zeit/next.js/pull/646的公关,但我们会将身份验证服务器移到其他地方
使用graphql怎么样?
Apollo给出了一个graphql认证的例子:
https://dev-blog.apollodata.com/a-guide-to-authentication-in-graphql-e002a4039d1
在这里,我们正在验证一个 graphql 请求,但它可以适用于我们的情况。
加上 graphql 可以抽象出实现和逻辑。 它可以与无密码、auth0 或我们可能更喜欢的任何其他东西一起使用。
@impronunciable仅供参考,您的示例我还不知道如何处理会话,因为我想摆脱无密码。 我将尝试在我的应用程序中调整@iaincollins的示例。
我的要求是:
如果它可以帮助某人使用服务器端身份验证,这是我的应用程序🙂
身份验证应该从 Next.js 服务器中分离出来,但我正在等待其他人对此提供灵感......此外,我不确定它是否受到了 CSRF 的适当保护。
这就是我所做的:
这很容易受到 XSS(即使 react 做了很多来阻止它)和 CSRF 攻击,但它很简单并且可以与 SSR 一起使用。
只是为了添加到请求
graph.cool + apollo + jwt + auth0 + next.js,前 4 部分已经在https://github.com/graphcool-examples/react-apollo-auth0-example完成
@balupton在您的示例中,当令牌过期而用户仍处于连接状态时会发生什么,会话是刚刚结束还是以某种方式更新?
@nmaro不知道,不是我写的 - 最好去那边问问
对于我自己的应用程序,我使用 next.js 和 auth0 进行身份验证,然后使用 zeit/micro API 服务器来验证不记名令牌。
应该能够在 2 月份的某个时候开源。
遵循 next.js 一直遵循的相同理念(做一件事并做好),我为 node 开发了一个可扩展的用户帐户系统的框架。 请参阅此处的理念: https :
github 项目在这里: https :
目标是拥有一个可扩展的、独立的身份验证 + 用户管理服务,非常适合用作单独的微服务,这是一种与 node.js 配合得很好的应用程序结构。
电子邮件+密码身份验证示例如下: https :
Facebook 和 Google 身份验证(ooth-facebook 和 ooth-google)已经有包,它们应该很容易实现,分别基于passport-facebook 和passport-google。
我将尽快发布 next.js 集成示例。 随意加入讨论并做出贡献。
对不起,插件,但它是为了更大的好处 - 我真的相信 node 还没有好的解决方案,这正是可能想要这样东西的人的正确受众。 和平
同时......这是一个示例graphql API,它需要使用JWT-Token进行身份验证,仅用于写入操作。 随意将它与您最喜欢的身份验证方法一起使用:)
https://github.com/nmaro/ooth/tree/master/examples/graphql-api-with-auth
我已更新https://nextjs-starter.now.sh以添加 oAuth 支持。
oAuth 的性质 - db+sessions+passport 和错误处理需要紧密合作 - 以及需要同时在客户端和服务器上工作的会话以及它如何与通用渲染一起工作 - 意味着要尝试并立即解决一些问题,如果您试图弄清楚发生了什么,但客户端逻辑没有任何 oAuth 特定配置,因此它不会太混乱。
我很乐意将身份验证拆分为一个单独的示例,尽管我怀疑它不会小很多。 如果其他人愿意,那太好了。 我可能会在某个时候为示例添加更多内容(例如帐户管理页面)。 可能将更多链接到文档会很好。
惊人的工作! 如果您可以拆分身份验证并将其添加到 next.js 存储库中,那就太棒了 :heart:
顺便说一句,我也可以做到👍🏻
在接下来的 2 周左右我没有时间,所以如果有人想这样做会很棒。
我很想重构它,看看它是否可以简化为一个模块(暴露简单的组件,如登录按钮和要嵌入的登录表单),并从非常好的早期仅客户端示例中获得一些灵感通过@impronunciable。
更新:我实际上要离开了,这就是为什么我不能,但是当我回来时,我很高兴看到这样做!
我按照 auth0/react 快速入门指南进行了一些修改,但是当我调用lock.show()
,应用程序抱怨:
未捕获的错误:addComponentAsRefTo(...):只有 ReactOwner 可以有 refs。 您可能正在向未在组件的render
方法中创建的组件添加引用,或者您加载了多个 React 副本
@iaincollins @timneutkens关于你的例子,如果我错了,请纠正我。
该示例使用 httpOnly cookie 来存储会话令牌,使其可以安全地抵御 javascript 注入攻击 (XSS)。 然后它遇到了将 csrf 令牌存储在本地存储中以防止 CSRF 攻击的麻烦。
有一个潜在的假设,即这种技术的组合使事情变得安全,这可能会误导用户/开发人员。 攻击者仍然可以在页面 (XSS) 中注入 javascript,读取 csrf 令牌,并使用它对 apis 执行经过身份验证的 (cookie) 请求。 在自述文件中值得一提吗?
嗨@davibe
唉,目前没有比会话 cookie 和单独的轮换 CSRF 令牌更好的技术方法,所以我想我不得不说我对模型非常满意,因为没有其他方法可以做到这一点(即使实际实施总是可以改进的)。
我们可以添加浏览器指纹,也许会话令牌可以更频繁地轮换(我忘记了现在是否是自动的),CSRF 令牌可以是标头而不是参数,cookie 应该只在生产中使用 SSL,我们可以添加登录令牌的到期日期,但 AFAICS 就安全模型而言,改进的空间相当有限,并且如果有人能够将他们喜欢的任何代码注入到应用程序中,也没有什么能真正提供进一步的保护; 但请随时将这些问题作为改进 repo 的问题提出。
如果有任何帐户状态修改选项(目前没有),我们可以在执行它们之前先进行验证码,以便在未经许可的情况下更难以在服务器上进行更改,但示例中发生的所有事情都是用户可以登录和 out 所以这目前超出了范围。
将会话令牌存储在本地存储会使其安全性明显降低并违背规范中的预期用途,所以我个人不赞成这样做 - 尽管我很欣赏它会简化事情,因为没有 CSRF 投影,但人们可能不需要一个例子,因为它很容易做 - 我只是试图提供一个,因为它太尴尬了。 :-)
我完全支持你,因为我也讨厌它有多么尴尬,并且没有放弃尝试将它变成一个模块。
嗯……我明白了。
这个问题对我很有用,谢谢。
这是Auth0的观点https://auth0.com/blog/cookies-vs-tokens-definitive-guide/
他们基本上建议避免使用 cookie,但我认为这会在第一页加载时忽略 SSR。
嘿大家!
我也在研究 next.js 的身份验证以及来自这个问题的评论,特别是@davibe + repo 和 @timneutkens的PR提供了巨大的帮助。
我的解决方案执行以下操作:
这有助于我在 docker 容器中运行自己的 JWT 令牌身份验证微服务。
也许提供一个简单的 JWT 服务作为 with-auth 示例的一部分会更容易? 从而避免破解 server.js 并可能失去 next.js 内置热重载、ssr 和路由的好处?
我也不确定这种方法在 CSRF / XSS 方面是否安全。 欢迎提出任何意见。
感谢大家迄今为止所做的出色工作。 我是这个项目的忠实粉丝!
@jcsmesquita我正在开发一个新版本的示例,其中所有内容都与 Next.js 分离,类似于 zeit.co 上的 auth 实现方式。
这似乎很有用: https :
@subsumo感谢提及,仅供参考: NAP是我的,通过网络进行的身份验证基于@iaincollins nextjs-starter加上使用令牌对本机客户端登录进行反应。
@timneutkens您正在使用的解决方案是否允许您对单独的服务进行身份验证?
我一直在查看当前的身份验证示例拉取请求https://github.com/zeit/next.js/pull/1141
在我看来,它只允许向next.js 服务器进行身份验证,但不能同构地向单独的服务进行身份验证。
换句话说,假设您想将 next.js 服务器和实际的应用程序 API(例如 REST 或 GraphQL)分开,那么您想要做的是客户端和服务器能够对 API 进行身份验证。 我不认为这个解决方案真的对你有帮助。
我想到了一个流量。
实体:
目标是建立 3 个基于 cookie 的会话:
Session 1) 是客户端识别服务器,2) 3) 是客户端和服务器都可以使用各自的会话独立访问 API。
这是流程:
你怎么看? 有更容易的方法吗? 我们是否应该只保持 API 与 next.js 服务器耦合,以便只需要一个会话(CS)?
@rauchg我不会轻易召唤你,但我相信这也是关于 next.js 必须走的方向 - 作为“通用前端”,它是否应该与提供数据的 API 分开运行? 如果是,我们需要正确处理。
@timneutkens您正在使用的解决方案是否允许您对单独的服务进行身份验证?
是这样的想法。 一直忙于在 Next.js 上修复其他东西。 会尽快回复。
我将我的应用程序中特定于 github-auth 的部分分解为一个非常易于理解的示例。 对某些人来说可能很有趣,并且会喜欢关于代码或流程的任何反馈: https :
更新:我将示例应用程序重构为一组可重用的组件,这些组件可以“放入”下一个应用程序
后续:我写了一个与ooth和 GraphQL API 的集成。
https://medium.com/the-ideal-system/ooth-user-accounts-for-node-js-93cfcd28ed1a#.ykoj1dhil
它基于现有方法,即对next.js 服务器的身份验证,即它假定 api 和身份验证服务器都在同一进程中运行,因此只需要创建一个会话。 优点:原则上它可以存储 / 的凭证,几乎可以扩展到任何passport.js 策略。
@timneutkens在这方面有什么进展吗?
我将我的 github auth 示例应用程序重构为一组可重用的装饰器和页面组件,用于“将 github auth 放入”下一个应用程序。 欢迎代码和功能反馈。 https://github.com/possibilities/next-github-auth
我_认为_在这里确定板子然后继续将其发展为更通用的身份验证框架可能会很有趣。
@timneutkens
很抱歉打给您,但您在这方面取得了任何进展吗? 我完全不知道应该如何正确设置 next.js 身份验证。
@kolpav我已经做了一些工作,目前被其他东西淹没了😥 无论如何,这在我下一步的优先事项列表中非常重要😄
使用 next.js 应用程序实现身份验证的清晰、有据可查且安全的方式对其作为框架的成功至关重要。
我不记得上次我构建了一个没有身份验证的网络是什么时候。
像我想象的大多数人一样,我也想对我的后备数据进行安全调用,因此 JWT 似乎是显而易见的解决方案。
但是在问题和公关之间有太多的讨论,我不知道从哪里开始!
@timneutkens
酷👍我认为它对其他人非常有价值。
@camstuart @kolpav上面有一些很好的工作示例,包括支持 oAuth 和基于电子邮件的身份验证,它们由贡献者@jaredpalmer 、 @luisrudge 、 @impronunciable 、 @possibilities和我自己提供,使用 JWT 和 HTTP Cookie。
要突出显示一些链接,请检查:
(微认证示例很好,但我认为它需要更新。)
有进一步改进的余地,其他评论员在上面评论了这些 - 包括会话存储组件和拆分服务器逻辑; 还有一个例子,进一步简化了 Tim 一直在研究的事情。
身份验证的简单性对于 Universal 应用程序来说是一个具有挑战性的领域,但是您应该能够继续执行上述示例 - 或者直接尝试演示 - 并查看它们如何工作而不会大惊小怪。
@iaincollins这是很好的例子。 但是我如何使用(例如)启动项目? 所以如果我想构建我的应用程序。 我需要克隆这个 repo 吗? 或者我需要将起始项目中的代码逐块“复制粘贴”到我自己的代码中?
如果启动项目将被更新 - 我应该怎么做?
@iaincollins
很好的例子,尤其是你的。
但是,我仍然希望看到带有 zeit 批准印章的 auth 示例😄 所有的眼睛都会指向一个方向,因此任何错误或错误都不会被忽视。 目前,我有自己的工作身份验证,但我不确定它的安全性。
同意@kolpav ,推出自己的安全性是一项棘手的工作。 最好留给专家
我为此创建了一个堆栈,可以使用 GraphQL 轻松处理身份验证: https :
@salmazov我找到了一种有用的方法来开始并了解示例或入门项目中发生的事情,可以将其分叉,然后去掉不相关的东西,直到只剩下与功能相关的代码你想实施; 然后尝试将该功能移植到另一个项目中。
@kolpav @camstuart该线程包含对各种安全模型的广泛讨论,包括揭穿一些常见的误解和权衡。 我特别要注意有关 HTTP Only Cookies 和 CSRF 令牌的要点(以及它们通过使用 JWT 和/或 Web Storage API 为会话令牌提供针对 XSS 和 CSRF 的额外保护)。 真的很值得一读。
@iaincollins你是想链接一些东西吗? :微笑:
大家好 :)
我有一个问题,我正在阅读 next can't get the tokens for auth with jwt from localstorage.
如果我有一个服务器来渲染我的网站的第一笔费用。 我的 api 有另一台服务器。 这从客户端接收用户/通行证并给出一个 jwt。 在哪些情况下我需要在服务器中获取令牌?
为什么我需要在渲染服务器中使用令牌(下一个)?
如果客户端不将令牌发送到 api 服务器,则 api 不提供数据,用户将无法获得私人信息。 我不明白为什么我需要将令牌发送到渲染服务器。
@kamilml
这可能会有所帮助。 一般来说,您有两种选择:
给 Next 服务器 JWT(可能通过 cookie)。 这将允许 Next 服务器代表客户端进行 api 调用。 如果完整的服务器端渲染对您很重要,您会想要这个。
将 JWT 存储在客户端本地存储中,不要让 Next 服务器访问它。 在这种情况下,您可以简单地绕过 api 调用服务器端并推迟完全渲染,直到客户端加载完成。
很抱歉重新打开这个,但我想我会在这个线程中添加我的 2 美分,以及我最初的研发在这个领域是如何发展的。 更少的代码示例,更多的高级流程。
首先,就上下文而言,我们的大部分应用程序已经构建在 Symfony 3 (PHP) 中,并使用 Vue 来实现混合体验。 服务器呈现一个包装页面,并将应用程序数据分配给__INITIAL_STATE__
以供应用程序获取。 这导致在 Symfony 中呈现营销页面(用于 SEO)和在其他领域通过 JS 获取数据选择 UX/UI 而不是 SEO 之间做出决定,并提供更多 SPA 风格的感觉。 同样值得注意的是,并非所有页面都是二进制的公共/私有页面(正如我在一些示例中看到的那样)。 某些页面默认是公开的,如果经过身份验证,则呈现不同的页面。 我们曾考虑在网站的某些部分使用 SPA,但在许多实际方面,它的 UX/UI 更差(TTI、进度条等)。 另外,这并不能解决 SEO 问题,除非我们引入 FOUC 并渲染文本两次(一次通过 Symfony,再次作为 JS 组件),等等。
进入SSR/通用JS...
就我而言,我所做的是模仿PHPSESSID
逻辑,通过创建HttpOnly
UJSSESSID
cookie(命名),并将值设置为用户的智威汤逊。 当用户浏览站点时,Symfony 应用程序在每个页面请求中传递这个信息。 当用户点击 UJS 页面时,这些应用程序的服务器端将收到请求中的 cookie(根据浏览器的内置行为)。 如果设置了UJSSESSID
cookie,应用程序将调用 API 以获取用户信息(例如, /api/v1/users/me
通过Authentication
标头传递令牌)。 其余的调用是通过 API 完成的,使用相同的令牌。 Symfony 注销机制会清除UJSSESSID
cookie。 下次 UJS 应用程序加载时,它将以匿名用户模式呈现页面。 仅供参考,Symfony 与 UJS 页面路由是通过 Apache 的ProxyPass
。 不使用 LocalStorage。
最终的结果是一个无缝的 UX,其中用户在一些页面是 PHP 和客户端 JS,而一些页面是 UJS。 这使我们能够进行 A/B 测试,并迭代更新站点——不是每个人都可以从头开始:)
虽然由于 PHP/UJS 共生,这有点复杂,但相同的原理可以用于具有 API 或 Node.js 服务器中间件(例如 Express、Adonis 等)的完整 UJS 解决方案。 不要通过 PHP 页面请求( HttpOnly
标志)设置UJSSESSID
cookie,而是让用户通过您的 SPA/UJS 登录,并在那里设置 cookie。 您不应该做的是使用您的应用程序解码 JWT,或进行需要client_secret
第 3 方调用。 为此使用留在服务器上的中间件。
希望能帮助某人。 我见过的其他例子对我来说有点太培养皿了。
@jaredpalmer嘿,谢谢你的实现,我试过了,只是复制粘贴了你所有的代码,用我的 index.js 替换了你的dashboard.js,它看起来像这样:
const index = () =>
<div>
<span>WoooHoooo</span>
</div>
export default withAuth(index)
在 withAuth hoc 中,我将其更改为重定向到登录页面。
但是在它重定向到登录页面之前,索引页面的内容仍然闪烁了一下。 :S
这个问题的状态如何? 😇
对于阅读整个讨论的新人来说,这有点不知所措。 我决定在这里实施非常基本的身份验证。 只有 2 个页面(索引、登录)和一个自定义服务器
https://github.com/trandinhan/next.js-example-authentication-with-jwt
基本上,我们在服务器中有一个身份验证中间件来检查每个请求头中的令牌。 jwt-token 将存储在 cookie 中。 我发现这非常简单、直接并且工作得很好。
@trandainhan你能添加一个使用秘密令牌来防止 CSRF 攻击的 POST 端点吗?
@sbking使用受 CSRF 攻击保护的示例端点更新了源代码
准备好使用了吗😬?
有人尝试过使用redux-auth-wrapper 进行身份验证吗?
大家好! 在过去的几个月里,我创造了,现在精炼了
它的特点是:
在此处查看现场演示: http :
我这样做主要是为了快速原型设计,快速开始使用一个简单的、不依赖外部服务的工作帐户系统的应用程序。 我对结果很满意。 我打算继续使用和维护这些库,所以如果您觉得仍然缺少一个既定的帐户系统 + 用于节点的 UI,请试一试。
@trandainhan谢谢,这是一个非常好的例子,对很多人来说更简单,并且可以在很多场景中使用。
我将考虑是否/如何调整 nextjs-starter 中的当前逻辑以使用类似这样但安全的东西,同时仍然与我拥有的真实世界用例的快速会话逻辑兼容(例如使用诸如 Google oAuth API 之类的东西,我需要服务器在其中保留和跟踪首次登录时授予的令牌)。
我还没有弄清楚这是否可能,但如果可能的话,对人们来说会容易得多。
如果没有,至少值得在某处写一篇好文章向人们解释不同的选择。
@trandainhan :如果我将<Link href="/">Home</Link>
到 login.js 中,然后单击生成的链接,那么我可以在不登录的情况下访问 index.js。您建议如何在示例中解决此问题?
@iaincollins我在这里看到大多数解决方案都是针对 oauth 服务进行身份验证的。 是否有针对依赖 JWT 的 API 进行身份验证的好的解决方案?
@paulwehner我认为这是因为@trandainhan只处理了服务器端身份验证路由。 我仍然不清楚客户端路由如何在 next.js 中工作,因为一切都由 next/Link 组件在后台处理。
@carlos-peru 在幕后,Link 实际上使用 next/router 将新路径推送到浏览器历史记录中。 似乎大部分事情都是由浏览器处理的,这里对于服务器端的中间件没有什么可做的。 到目前为止,我只能想到创建一个我们自己的 Link 组件并在我们更改 url 时做其他事情。
@carlos-peru 您始终可以使用自定义服务器来完全控制路由。
感谢@trandainhan和@kolpav! 在周末度过了一段时间后,我有了更好的理解。 我还设法实现了一个依赖于 HOC 的解决方案,该解决方案将 jwt 令牌保存在 cookie 上,因此服务器和客户端都可以使用 api。
@nmaro我有一个带有 JWT 身份验证的后端,用于 next.js 中的网站和一个应用程序(react-native)。
所以。 我认为在下一个模型中:客户端获取 Facebook 令牌(用户接受 facebook 登录),然后客户端将令牌发送到后端以检查和验证用户令牌(node-js 后端中的 passport-facebook-token) . 然后,如果令牌是好的,后端将后端生成的 JWT 发送给客户端。
问题? 您的工具可以从 facebook 和 google 获取密钥吗? 我正在使用 hello.js 但与 next.js 不兼容,并且全世界都使用passport-facebook。 我认为这是一个很大的错误,因为 api 服务器需要与 web 客户端和移动应用程序兼容。
谢谢
PS:我无法在您的松弛频道中获得邀请。
@nmaro我想帮助你完成你的项目。 我也有一个 Graphql 后端,带有passport-facebok 和passport-google-id-token !!!
但。 您知道在 next.js 中连接 facebook/google 客户端的兼容包吗?
在客户端,您可以使用与我在此处使用的模式类似的模式: https: //github.com/nmaro/staart/blob/master/packages/staart/src/components/login-facebook.js https://github。 com/nmaro/staart/blob/master/packages/staart/src/components/login-google.js
(而不是oothClient.authenticate
只是向您的身份验证路由发送一个发布请求)。
@timneutkens会欢迎带有最小带牙示例的 PR 吗?
@nmaro在你的例子中。 为什么要使用 componentDidMount 而不是 componentWillMount?
我正在为 Google 登录制作一个 HoC(提供程序)组件。
componentWillMount 是否只在客户端调用? 那么应该没问题。
好的。 非常感谢你的例子。 最后,我可以通过社交媒体实现身份验证。
最后一个问题。 我创建了一个 access_token 和一个 refresh_token,我想放入 localStorage 来保存数据。
我可以将它放在 next.js 中的何处以在页面刷新时检查登录? 在 create-react-app 我把它放在 index.js
...
import { loginUser } from './actions'
...
let accessToken = localStorage.getItem('access_token')
let refreshToken = localStorage.getItem('refresh_token')
if (accessToken && refreshToken) store.dispatch(loginUser({accessToken, refreshToken}))
ReactDOM.render(
<ApolloProvider store={store} client={client}>
....
谢谢
使用 ooth,我不存储 facebook 令牌,我只将它们与护照一起使用一次,然后创建一个普通的基于 cookie 的用户会话。
啊。 好的。 我正在使用 JWT 而不是会话。 因此,我将 access_token 发送到后端,它检查令牌在服务(google、facebook)中是否有效,然后用户是否存在于我的应用程序中并向我发送 JWT。
谢谢 :)
我应该补充一点,将 JWT 存储在 localstorage 中是危险的(因为 XSS),如果它们都在一个服务器/域中,最好将它们作为 cookie 发送,因此客户端 javascript 代码无法获取 JWT,但浏览器会发送JWT 自动作为 cookie。 JWT 很棘手(请参阅上面的冗长讨论),这就是为什么我使用会话的原因。
仅当 ooth 用作外部身份验证微服务时,我才使用 JWT(因为 cookie 仅适用于同一个域),即使如此,我也只使用它一次,然后与服务器创建会话,因此它不会存储在客户端的任何位置.
为了增加 JWT 安全性,您可以使用刷新令牌(取自 oauth2)。
移动应用程序可以处理 Cookies?
@nmaro 奥基。 我理解你。 抱歉我的错误。
您是否有将 JWT 令牌保存到 Cookie 中的教程或代码? 我在看https://github.com/zeit/next.js/blob/master/examples/with-firebase-authentication (我的身份验证系统是 graphql)
不,对不起,我从来没有这样做过。
@hmontes @nmaro
我为自己写了这个,但可能会有所帮助:
https://github.com/malixsys/mobazoo
嘿@malixsys感谢分享。 我从您的代码中不明白的事情:您似乎会将 API_BASE_URL 配置为外部内容,对吗? 如果您对该 API 进行身份验证,我想它会启动一个基于 cookie 的会话,对吗? 但是,假设 next.js 服务器在另一个域中,您如何将该 cookie 传输到 next.js 服务器?
啊不,我明白了,您在同一过程中设置了 /auth/signin。 好的,那么它基本上与我使用 next.js / staart 用于 ooth 的方法相同。
实际上不,我仍然感到困惑:在 /auth/signin 中,您返回 JWT,但您从未在客户端对它做任何事情。 后来你“getUserFromCookie”,但我没有看到你在哪里设置 cookie。
@nmaro我只是想要一个基本的登录名来与通用一起工作。 我现在都设置了 jwt 和 cookie。 cookie是用来做通用的。 我还没有在这个 repo 的另一个 axios 调用中使用 jwt。 我在一个私有分支中做,其中 API_BASE_URL 指向另一个域......
这一切都在我的待办事项清单上,使用 PWA。
请随时打开一个问题或在这里ping我...
@nmaro cookie 设置在saveUser()
: https :
我知道这个问题已经结束,但我想展示我的解决方案。
https://next-auth.now.sh/
我觉得有点像zeit.co的网站
4月9日
我已经做了一些工作,目前被其他东西淹没了😥 无论如何,这在我下一步的优先事项列表中非常重要
9 月 22 日 #2974
我们计划很快发布一个官方认证示例,你能把它作为单独的存储库发布吗? Thaaaanks!
@timneutkens
嗨,很抱歉再次打扰您,但您能否分享官方身份验证示例的状态? 这绝对是我很想看到的,并根据本期其他人的评论数量来判断。 那么它在优先事项列表中有多高,我们应该抱有希望吗? 😄
嗨@kolpav ,他们已经在 Next.js 5 的路线图上正式宣布了这一点。
最后,我们添加了一些要求很高的示例(如用户身份验证)、改进的 Next.js 内部文档以及更小的功能和错误修复。
https://zeit.co/blog/next-canary#the -roadmap
@babenzele这是个好消息。 我一定是错过了😅
我们可以在哪里了解 Next.js 5 的最新发展? 我现在正在集成 auth0,但是如果有一个官方的 nextjs auth 路径/示例来遵循会很棒
似乎 Next.js 迫切需要使用 JWT/cookies/localStorage(或它们的组合,只要它是安全的并且不受 XSS/CSRF 保护)的官方本地身份验证示例......我花了几个星期试图使用带有 Passport.js 本地策略的单独 Express API 服务器来解决这个问题。 对于无状态请求,我在 cookie/localStorage 中尝试了 JWT,并且在我最终放弃 JWT 和无状态后,我还尝试了一个带有来自 express-session 中间件的会话 ID 的常规 cookie。 由于 express-session 的工作方式(尝试使用 saveUninitialized: false),我最终遇到了在 Express API 服务器上创建额外会话的问题。 我考虑将 Express 代码移到我的 Next.js 应用程序中的 server.js 中,但我真的希望它是一个单独的服务器。 我也很确定我的实现对于 XSS/CSRF 或 cookie 劫持并不安全。 我们需要一个涵盖本地身份验证/登录最佳实践的官方示例,或者作为 Next.js 一部分的官方模块,它将为我们处理复杂性!
在我们等待 Next.js 5.x 和更多示例的同时,您可能想查看https://nextjs-starter.now.sh ,它仍在积极维护中,并使用 Next.js 和 Express、Express Sessions、CSRF( CRSF 令牌)、XSS(用于会话令牌的 HTTP Only Cookie)并使用 Passport JS 来支持 oAuth 和电子邮件。
我实际上正在将身份验证代码重构为一个模块,这使得以易于使用的方式向 Next.js 项目添加身份验证变得非常容易。 该模块应该允许您轻松地将它用于您喜欢的任何数据库,而无需从 Starter Project 示例中复制一堆代码。 我希望这周能完成它。
作为参考,如果您正在寻找一种简单但安全的方法,“双重提交 Cookie”方法提供了一种添加 CSRF 保护的简单方法。
localStorage 中 JWT 的问题在于它始终可以从客户端 JavaScript 读取,因此如果您有不受信任的内容(例如用户提交的内容或广告),它是通过 XSS 进行会话劫持的载体。
如果您将 HTTP Only cookie 用于会话令牌 - 或者像在 HTTP Only 中带有令牌值的 JWT(或加密整个 JWT 并使用服务器上的 HTTP Only 令牌对其进行解密) - 那么会话将受到尽可能的保护是。 这个想法只是,理想情况下,会话令牌不应该通过客户端 JavaScript 读取。
当然,很多站点不使用仅 HTTP cookie,因为这对单页应用程序来说很痛苦,并且总是涉及在前端有一些身份验证逻辑,但这仍然是理想的方法。
另一个选择仍然是https://github.com/nmaro/ooth ,它正在积极维护,已经打包并正在几个生产应用程序中使用。
@iaincollins我下载了 Next.js Starter Project 并开始浏览它。 我需要分离出与安全相关的重要 (XSS/CSRF) 部分并尝试将它们集成到我的应用程序中,或者等到您完成单独的模块。 有什么地方可以跟踪该模块的开发吗?
嗨@kelleg1 ,
单独的模块现在作为next-auth模块发布,以便于在其他项目中使用。 它包含一个示例项目,展示了如何使用它。
作为进一步参考, nextjs-starter.now.sh项目现在也使用 next-auth,这极大地简化了入门项目中的代码 - 现在添加对新 oAuth 提供程序的支持或将其用于不同的数据库变得更加容易(尽管该示例仍然使用 Mongo DB)。
虽然它仍然有些复杂,所以如果你有一个现有的应用程序,你可能会发现它更容易用作参考,但如果是这样,我希望它有所帮助
注意:CSRF 目前仍然非常紧密地耦合在其中。 它使用 lusca,因此假设 res.locals._csrf 已设置,但不同的 CSRF 库使用不同的私有变量。
我很欣赏它的使用比任何人想要的都要复杂,但至少现在身份验证代码最终被分离到一个模块中,所以我可以开始重构。 随着时间的推移,我希望让它更容易使用(使用不同数据库的默认处理程序,以及更简单的 oAuth 配置)。
@iaincollins看起来对 next.js 的唯一依赖是https://github.com/iaincollins/next-auth/blob/master/index.js#L342 ? 如果是这样,最好使 lib next.js-agnostic。
@sedubois完全同意!
虽然这可能是一个很好的讨论,因为它是 GitHub 存储库问题,而不是这里。 🙂 如果您想建议可以改进、简化和使其更通用的方法,那么您很乐意合作。
(让 next 尽可能易于使用仍然是主要目标,但我认为它不需要是排他性的,即使它最终也有下一个特定的重点选项。)
@timneutkens恭喜 nextjs 5 发布。在不久的将来,我们会看到在 examples 文件夹中添加了一个官方本地身份验证示例吗? 或者这是否仍在进行中以供以后发布?
Ooth 现在有一个全面的文档,包括next.js 身份验证的细节。
@jaredpalmer我喜欢你的方法,我复制了你的部分实现。 但是, getUser
方法安全吗? 如果有人手动更改 localstorage 中的字符串怎么办,应用程序依赖于此是否明智? 将其更改为每次解码令牌的 JWT 公共部分并从那里读取用户状态的方法是否更有意义? 这样一来,由于事物的 JWT 性质,我们可以更加信任这种状态。 你有什么意见?
您应该将其存储在 cookie 中。 我是在 Next 支持自定义服务器之前写的。
@jaredpalmer你能告诉我更多吗? 我们应该将所有内容存储在 cookie 中而不是本地存储中吗? 你的 HOC 也会不同吗? 这是否意味着我们现在可以使用getInitialProps
方法来渲染受保护的网站?
请注意_如果您所说的存储是本地存储/网络存储中的敏感数据_:
“永远不要使用 Web Storage 存储敏感数据:Web Storage 不是安全存储。它并不比 cookie“更安全”,因为它不是通过网络传输的。它没有加密。没有安全或 HTTP only 标志,所以这个不是保存会话或其他安全令牌的地方。”
登录后在 cookie 中设置令牌。 使用 cookie-js。 然后在服务器上使用 express cookie 解析器,以便您可以检查 get req.headers.cookies.myToken 或等效项。 在 hoc 的 getInitialProps 中检查 req 是否存在,然后从 req.cookies 获取令牌,否则从 Cookies.get('mytoken') 获取它到客户端。 此时,您将可以访问客户端和服务器上的令牌。 然后,您想要创建一个 fetch/axios 包装器/实例,并将其与 getInitialProps 中的 next ctx 合并,以便您的所有页面都有一种方法来进行经过身份验证的同构请求。 您可能还想让您的用户也参与其中。 所以你不需要到处重复。 如果你需要像 withUser(withTeam(Page)) 这样的常见实体,你可以做更多的 hoc
getUser 是一个坏主意,您应该只存储令牌。
@jaredpalmer我建立了一个几乎完全一样的方法,一切正常。 我现在要解决的问题是如何刷新令牌。 我正在使用的 API 具有相对短暂的令牌(2 小时),我正在尝试了解一些系统,以在使用应用程序时保持用户登录。
你对此有什么意见吗?
您还可以通过不通过 next.js 为您的用户传递数据来保存每个页面转换的请求。 为此,您需要从 server.js 中的 cookie 中读取令牌,并尝试获取用户并将其传递给下一个请求处理程序。 在文档中,从 props 中获取它并将其存储在 JSON 中,例如 window.USER。 然后在您的 hoc 中,您只需在客户端时从窗口读取它。 最后,您应该使用带有响应拦截器的 axios,它会在收到 403 代码后立即清除您的 cookie 并重新加载页面
@pbrandone尽管重复,但最简单和最可预测的解决方案是随每个请求发送您的令牌,在响应的标头/正文中接收一个新刷新的令牌。 您的客户端会在每个请求/响应周期更新其已知值,从而有效地授予永远不会过时且不会被滥用的一次性令牌。
通常情况下,用这种方法, /token/refresh
仅用于应用程序的最初的“唤醒”(认为componentWillMount
用于钩<App/>
),检查是否已保存的最后令牌仍然有效和可用。
从来不必处理刷新令牌,但我可能会尝试在 axios 拦截器中执行此操作。 将不得不考虑更多,但理论上你会拦截一个错误的请求,然后使用刷新令牌获取新令牌,使用新令牌设置 cookie,然后再次使用新令牌重放第一个请求。 Axios 拦截器非常强大,因为它们允许您将此类内容抽象化并从您的产品代码中消失。 您可能需要编写一个请求和响应拦截器和/或持有某种有状态的进行中对象/映射,这可能会变得很麻烦。 希望有帮助
或者按照卢克说的去做。 容易多了。
如果要刷新令牌,则需要通过自定义服务器设置拦截 cookie(如果您使用 cookie)。 我是用快递做的。 否则,您可以使用 JWT 令牌在客户端完成所有操作。 因为我是通过 cookie 做我的,所以我有 2 个用例 - 在客户端处理导航请求,在服务器端处理页面请求(任何时候有人手动输入 url 或点击页面刷新。)但基本上它们是相同的流程只是执行不同,因为一个是客户端,一个是服务器端。 在服务器端,因为我使用了 express,我只是创建了一个中间件来处理 cookie,解码其中的 JWT 并检查过期时间。 如果它已过期,则请求获取新令牌并继续将解码的用户传递给客户端上的 redux。 在客户端上,我有一个用于安全链接的 HOC 包装器,每次您导航客户端上的某个位置时,它都会不断检查用户。 HOC 将获取您的 cookie 或 JWT(在我的情况下,我的 JWT 在 cookie 中)并快速检查它是否仍然有效。 如果它无效,它将尝试获取刷新的 JWT/Cookie,然后继续,否则它会将您注销。 是的更复杂,但也更安全。 希望有帮助! 如果您有兴趣,请随时提问 - 我花了一段时间才弄明白。 我个人想知道如何使用 Github 或 FB 等登录人们 - 在很多情况下可能没问题,但在某些情况下它只是不专业。 我还没有看到银行、医疗保健、我支付的任何账单等 - 用我的 FB 帐户登录。 不过可能只是时间问题;)
至于刷新访问令牌;
我不确定这是否是一种正确的方法,但我的访问令牌的生命周期很短(一分钟左右)。 为了处理客户端令牌过期问题,我采用了这种方法;
每次加载页面时,它都会检查令牌是否已过期,如果是,则刷新它。 但是,如果它仍然有效,它会调用检查到期前多长时间的方法,并设置超时以在到期时刷新访问令牌。 然后只要访问者在站点上就重复此过程。
callRefreshAuthenticationLater(){
clearTimeout(this.accessTokenRefreshmentTimeout)
this.accessTokenRefreshmentTimeout = setTimeout(() => {
this.refreshAuthentication()
}, this.authService.howMuchUntilExpiration(this.authService.getToken('access_token')))
}
这将返回到到期的毫秒数:
howMuchUntilExpiration(token){
return normalizeTimestamp(jwtDecode(token).exp) - Date.now()
}
非常欢迎对这种方法的任何反馈
@kunokdev - 我认为这是一个好的开始。 我移动了一个类似的 func 我必须计算剩余时间到一个 func ,它根据它是否过期而返回一个布尔值,这使我的代码更具可读性和简单性,而且我不需要担心重置计时器。 然后我只检查用户是否已过期,是否需要任何类型的需要身份验证的请求。 如果我确实有倒数计时器或类似对客户端或调试可见的东西,那么您的功能将是理想的。 那是我的 2 美分👍
身份验证/登录示例不应该是最基本的示例吗? 即内存存储,没有 JWT 或奇怪的令牌东西。 通常,当您想到某人“登录”时,这意味着他们有一个 cookie/活动会话。
我有一个登录 HOC 工作客户端和服务器端。 它使用 JWT 和后端(可以是节点、django 等)。
检查一下,任何反馈都非常感谢
https://github.com/hugotox/AppStarter
相关代码在这里https://github.com/hugotox/AppStarter/blob/master/src/components/auth/login-required.js
@hugotox我喜欢使用 Redux 存储来存储身份验证数据的想法,但是当您多次从getInitialProps
调用store.dispatch
getInitialProps
准备好应对不良副作用,即使身份验证过程也会呈现装饰组件还没有完成。 让我们从您的代码中获取使用示例:
export default withRedux(initStore, mapStateToProps)(
loginRequired([PUBLIC])(MyPage)
)
在getInitialProps
您在verificationOk
verifyToken
之前调用MyPage.mapStateToProps
将被调用 2 次(如果它侦听 store.auth.user)和第一次store.auth.user
即使对于需要登录用户的页面,
昨天我还发布了自己的 Next.js 驱动的入门套件的第一个 WIP 版本,但我从 Docker、Flow 和 fast-redux 开始: https :
我将专用的 Docker 映像用于可以在不同域上运行的 API 服务器和 web 应用程序,因此我仍在寻找一种可行的解决方案,该解决方案将允许登录真实用户,但也可以为移动客户端颁发 OAuth 不记名令牌。
@spencersmb干杯! 这几乎正是我要做的。 就第一次访问(从未登录)时 URL 的服务器端呈现而言,您是否只是拦截它,例如看到没有 cookie 并重定向到 pages/login.js 类型的页面?
@iaincollins JWT 和无会话应用程序/身份验证有其优势,很多人/公司(包括 Google)仍然希望使用它们。 阅读了针对 JWTs的
我只是随意地关注这个,但我在通用 JS 身份验证方面的经验是将 JWT 存储在HttpOnly
cookie 中。 当您可以选择使用应用程序的服务器端来存储 cookie 时,真的没有理由使用localStorage
。 如果您需要访问 JWT 以在浏览器中进行跨域 API 调用,那么您可以在__INITIAL_STATE__
类型的场景中传递 JWT(注意 XSS 漏洞,但至少您没有“存储”它在客户端)。 对于同源访问,cookie 将被来回传递(假设您使用withCredentials
用于 axios,或credentials: 'include'
用于获取),完全不需要将 JWT 放在 JS 中. 您可以在应用程序的服务器端使用代理从HttpOnly
cookie 中获取 JWT,_then_ 也可以进行跨域 API 调用。 在这种情况下,您也否定了预检调用。 每个人都有自己的,但我个人认为通用应用程序不需要 localStorage。
@bjunc是的,在关于 JWT 应该存储在哪里的所有选项中(localStorage vs. HttpOnly cookie vs. Redux,或者你有什么),我可能是错的,但我认为答案基本上应该总是一个 HttpOnly cookie。 这似乎已在众多博客和论坛上无数次提及。 (我不确定刷新令牌——也许它们也应该存储在 HttpOnly cookie 中?)我认为 JWT 的存储位置应该是一个已解决的问题,除了 JWT 的其他优点/缺点之外。
在我开始采用 nextjs-starter 和 next-auth 之前,我尝试或多或少地按照您在我的项目中所说的去做。 已经有一段时间了,但如果我没记错的话,我认为我遇到的问题是我正在验证的 Express API 服务器(使用 express-session)没有正确初始化/获取会话。 我会得到奇怪的行为,其中会话将被初始化多次。 如果我能解决这个问题,我打算或多或少地继续做你描述的事情。 我还将继续使用 nextjs-starter 和 next-auth,因为会话确实消除了 JWT 带来的许多其他问题,例如如何使令牌无效。
同样,官方示例(或示例)会有所帮助。 关键词:官方,意思是Next.js的作者考虑了所有的可能性,并把最好的想法和实践融入其中。 理想情况下使用现代 ES6/ES7/ES8 功能,例如 promises 和 async/await。
@kelleg1听起来您的问题与 cookie 创建的细节有关。 例如,您可能会因使用不同的设置( HttpOnly
、域、路径、过期时间等)而意外创建多个具有相同名称的 cookie; 这可能会产生像您所描述的奇怪的副作用。 一种调试方法是使用开发工具 Application->Cookies。 如果您看到一堆同名的 cookie,那么这可能会为您指明正确的方向。
无论如何,我不在核心团队中,所以我无法提供“官方”贡献(事实上,我使用的是 Nuxt.js,而不是 Next.js),但原理是相同的。 我尝试了几种不同的方法来做到这一点(权衡 JWT、cookie、CSFR、websocket CSWSH、localStorage 等的优缺点)。 我最终得出的结论是 Next/Nuxt 的通用性非常适合使用HttpOnly
JWT cookie。 也许其他人会得出不同的结论,但我个人并不站在“天哪,不要使用 JWT,你没有读过那篇文章说 JWT 会让你患癌症!?”。
@iaincollins很抱歉再次将其带回来,但网络上的每个教程都使用 localStorage 来保存令牌,而您说的是不安全的。 如果是这样,我们应该在哪里存储令牌?
饼干! :D
我们使用这样的东西:
// auth.js
async signIn(res, info) {
const { email, password } = info;
const result = await this.service.signIn(email, password);
if (!result) {
res.status(HttpStatus.BAD_REQUEST).json({ success: false, message: 'Wrong email and/or password' });
return;
}
const payload = {
scope: 'appUser',
userId: user.email,
language: user.language,
iat: moment().unix(),
exp: moment().add(7, 'days').unix(),
type: user.type,
status: user.status
};
const token = jwt.sign(payload, ...);
res.cookie('token', token, { domain: 'yourdomain.com', path: '/', secure: true, httpOnly: true, maxAge: 24 * 60 * 60 * 1000 * 7, signed: true });
res.status(HttpStatus.OK).json({});
}
// pages/index.js
class Index extends PureComponent {
render() {
return (
<Layout title="home">
<Home />
</Layout>
);
}
}
Index.getInitialProps = async ({ req, res }) => {
const auth = req ? getServerSideToken(req) : getClientSideToken();
return { auth };
}
export default Index;
// utils/auth.js
const decode = ({ token }) => {
const decoded = jwt.decode(token);
const { userId, type = 'anonymous', status, language = 'en' } = decoded || {};
return { user: { email: userId, type, status, language } };
};
export const getServerSideToken = (req) => {
const { signedCookies } = req;
if (!signedCookies) {
return {};
}
try {
return decode(signedCookies);
} catch (parseError) {
console.error(parseError, signedCookies);
return {};
}
};
export const getClientSideToken = () => {
if (typeof window !== 'undefined') {
const user = window.__USER__ || {};
return { user };
}
return { user: {} };
};
// pages/_document.js
export default class extends Document {
static async getInitialProps(ctx) {
const props = await Document.getInitialProps(ctx);
const info = getServerSideToken(ctx.req);
return { ...props, ...info };
}
render() {
const { user = {} } = this.props;
const json = JSON.stringify(user);
return (
<html lang="en">
<Head>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta charSet="utf-8" />
<link href="//fonts.googleapis.com/css?family=Kadwa:400,700|Work+Sans:100,300,600" rel="stylesheet" />
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.14/semantic.min.css" />
<link rel="stylesheet" href="/static/site.css" />
</Head>
<body>
<Main />
<script
dangerouslySetInnerHTML={{ __html: `__USER__ = ${json};` }}
/>
<NextScript />
</body>
</html>
);
}
}
// somewhere.js
import axios from 'axios';
axios.defaults.withCredentials = true;
axios.post(`${apiBaseUrl}/some/path`, data)...
// somemiddleware.js
const { signedCookies = {} } = req;
const { token = '' } = signedCookies;
if (token) {
try {
const info = jwt.verify(token, ...);
req.user = await this.getUser(info.userId);
if (req.user) {
next();
return;
}
} catch (err) {
console.error({ permissionError: err });
return res.status(500).json({...});
}
}
return res.status(403).json({...});
有点笨拙但非常安全,因为客户端 javascript 永远不会看到令牌,而 cookie 是 httpOnly、安全、签名并由axios
或其他人自动发送...
大家好,刚刚创建了一个带有 cookie 和 redux 的身份验证示例。 看这里https://github.com/zeit/next.js/pull/4011
等等,这个问题最终在哪里? 我从来没有在官方仓库中找到 Express auth 示例。
我看到很多关于 cookie 和 session 的讨论,但是当我的本地移动应用程序需要访问 API 时,它如何工作?
可以做到,但这就是 JWT 的用途:p
我刚刚详细阅读了整个线程,并觉得有必要总结一下我的发现。
似乎有两个可行的入门项目,其中包含可靠的身份验证模块:
@iaincollins和@nmaro 的出色工作!
谢谢@curran :)
我在这个 Pull Request https://github.com/iaincollins/nextjs-starter/pull/86 中记录了我为去除 nextjs-starter 的无关方面所做的所有代码更改
这可能更接近 Next.js 存储库的可靠准系统身份验证示例。
对于任何对适用于客户端和服务器端的简单 JWT 令牌化身份验证感兴趣的人,我在 Spectrum 聊天中得到了一些帮助,并认为我会与大家分享。 任何反馈总是受到赞赏。
嗨,大家好!
这是我几个月前构建的使用 next.js 进行身份验证的另一个示例,也许有人会发现它很有用:
此处(以及示例文件夹中)的许多身份验证示例从getInitialProps
返回用户会话/令牌/等。 据我了解,当页面在服务器端呈现时,这意味着用户会话信息将作为 HTML 页面响应的一部分(作为NEXT_DATA
)发送到浏览器。
当getInitialProps
在服务器端运行时,我看到此模式存在两个安全问题:
1) 用户会话通过网络从服务器传输到浏览器。 如果任何此类请求使用http://
而不是https://
,则用户的令牌等将以纯文本形式通过网络公开。
2) 用户会话作为 HTML 页面的一部分(在NEXT_DATA
脚本标签中)从服务器返回到浏览器。 将用户的令牌等直接放在 HTML 页面上似乎有风险,尤其是在页面被解析、由浏览器呈现并且其他 3rd-party 脚本现在可能正在运行时。
这些问题是否已经得到解决? 是否有针对这些威胁的缓解措施?
这就是我使用cookies的原因。 在此处查看我的示例https://github.com/hugotox/nextjs-starter/blob/master/pages/_app.js
最后! 在这里你可以找到一个完整的 dockerized next.js auth 示例,其中包含以下容器:
一切都与 docker-compose 结合在一起。
关于 JWT 的快速说明。 JWT 具有无状态、适用于移动设备以及需要将凭据从一个域传递到另一个域的优点。 主要缺点是它们将浏览器暴露给 XSS。 对于这个例子,我选择了一个纯粹的基于 cookie 会话的解决方案。 由于反向代理(所有微服务都在同一域下运行)和共享会话存储,我仍然设法在微服务中拆分。
https://github.com/nmaro/staart/tree/master/examples/staart
像往常一样,现场示例是: https :
我认为澄清一下是明智的,使用 JWT 本质上并不会将您“暴露”给 XSS。 相反,如果您的站点存在 XSS 漏洞(不是由于 JWT 本身),则 JWT 可能会受到威胁(以及任何其他脚本可访问信息); 而httpOnly
cookie 将无法访问。 没关系,您可以使用 JWT 作为httpOnly
cookie 的值!
仅使用 Cookie 的解决方案可以很好地用于同域通信,但是如果您有无头 API(例如example.com
调用api.example.com
),那么除非您想代理浏览器,否则这不是真正的解决方案从example.com
到api.example.com
的请求,通过对example.com
进行 API 调用并将它们转发给附加到请求的 cookie(它有自己的一组优点/缺点) .
我个人觉得 JWT 的缺点被夸大了,并且可以通过一系列常用的保护措施轻松缓解。 其中最重要的是,如果令牌在到期前已被泄露,则引用jti
声明(例如 Version4 UUID)的令牌黑名单。
嘿@bjunc是的,感谢您的澄清。 正确,JWT 本身不会让您暴露于 XSS。 关于你的其他观察,我想补充一点,他们每个人都有自己的陷阱。
没关系,您可以使用 JWT 作为 httpOnly cookie 的值!
是的,我想补充一点,这消除了 JWT 在域之间传输凭据的一项优势。
令牌黑名单
那,以及刷新令牌的其他常见做法消除了 JWT 真正无状态的另一个优势。
这是我对情况的理解:
我写了一篇关于 Next.js 身份验证/用户帐户的 Medium 文章。
这是一个广泛的教程,是我近两年业余开发和思考的脑残(我对这个问题的第一次评论是从 2017 年 2 月开始的)。
https://medium.com/the-ideal-system/user-accounts-with-next-js-an-extensive-tutorial-6831cdaed16b
您一直将 JWT 与安全性降低相关联。 JWT 不会降低您网站的安全性。 它本身并不是 XSS 漏洞。 如果您有 XSS 漏洞利用,无论如何您都会遇到同样的麻烦。 即使使用httpOnly
cookie,攻击者也可能无法读取 cookie 值,但这并不重要,因为他们可以运行任意代码——比如会自动传递会话 cookie 的 XHR 请求(本质上充当CSRF 攻击)。 如果您的 API 是跨域的,并且您正在发出浏览器到服务器的请求,那么 JWT 无论如何都在代码中(无论是初始状态还是 localStorage)。 如果你使用 Axios,你甚至可能设置了全局默认值,攻击者只需要发出一个 Axios 请求,甚至不用担心身份验证。
此外,如果不谈论 CSRF,您就不能真正谈论 XSS; auth cookie 是专门针对的。 即使设置了反向代理,CSRF 攻击也会允许攻击者针对您的 API 执行经过身份验证的请求。
顺便说一句,仅仅因为您将 JWT 放在 cookie 中(例如,知道用户是否已登录),并不意味着您不能将 cookie 值(例如 JWT)用于跨域服务器到-页面加载时的 API 服务器访问(这也适用于反向代理场景)。 对于浏览器到 API 服务器的请求,您也不会或被排除在初始状态下传递 JWT。 它们并不相互排斥。
顺便说一句,我发现“无状态”JWT 的想法在大多数用例中被高估和限制。 例如:
can edit Post
”,而是“ can edit Post:1634
”)。您没有将所有这些都放入 JWT 中,这意味着您必须深入领域层(即数据库)才能找到答案。 你刚刚加载了资源,所以不妨把它放在应用程序的其余部分可以访问的地方,现在你有了状态。 我发现认为您需要了解的有关该主题的所有信息都将包含在令牌中真的很愚蠢; 同时保持它主要是匿名和轻量级的。 在不离题的情况下,在服务间通信中存在“无状态”请求的论点,但即使如此,我也觉得不切实际(至少因为它涉及将您需要了解的关于该主题的知识烘焙到 JWT 的概念)。 .
同时可用的 Ooth 身份验证策略(粗体新增):
@jaredpalmer你写的
和php一样,Next的原子单位是页面。 最酷的功能之一是它仅在请求时才延迟加载每个页面。 仅使用客户端身份验证但使用服务器呈现,该受保护页面的 js 实际上是由浏览器下载的。 将来当 Next 添加服务器工作流时,您希望能够在服务器上阻止渲染和重定向以完全防止这种情况。 这将需要 cookie、会话和 AFAIK 会话存储,但这只是执行此类混合应用程序的成本。
我们是2年后。 是否有服务器工作流程来防止为受保护的页面加载 js?
@timneutkens可能将受保护的内容放在其他区域?
我将如何完全阻止访问受保护的内容?
@lishine您的页面的getInitialProps
中有一个ServerResponse - 您可以轻松地重定向没有特权的人。
是否有使用 redux 进行身份验证的示例?
是否有使用 redux 进行身份验证的示例?
你可以试试这个使用 redux 的例子,看看它是否适合你......
您可以在本主题的某处找到它,但如果找不到,请看这里:
https://github.com/alan2207/nextjs-jwt-authentication
我认为这是使用服务器端 API 调用结果 getInitialProps 时更复杂的问题,因为虚拟 DOM 在 LOGOUT-LOGIN 操作后使用旧结果。 我正在考虑解决方案
_编辑_
这是我对 redux-observable 的回答
|侧面|身份验证|待办事项|
|---|---|---|
|服务器|true|获取初始数据(使用请求 cookie 代理)。|
|服务器|false|显示登录页面,登录后获取数据。|
|客户端|true|获取初始数据。|
|Client|false|显示登录页面,登录后获取数据。 (这仅在页面移动时会话过期时发生)|
static async getInitialProps({ store, query: { rowsPerPage, pageIndex }, req, auth }) {
store.dispatch(TemporaryStoryActions.initPageState());
const isAuthenticated = () => req ? req.isAuthenticated()
: store.getState().auth.isAuthenticated;
if (isAuthenticated()) {
// fetch initial data
const TemporaryStoryApiProxy = withCookieProxy(req, TemporaryStoryApi);
await TemporaryStoryApiProxy.fetchTemporaryStories({
rowsPerPage: rowsPerPage || 15,
pageIndex: pageIndex || 0, }).then(json => {
store.dispatch(TemporaryStoryActions.loadTemporaryStories(
json.rowsPerPage, json.pageIndex, json.count, json.rows));
}).catch(error => {
if (error.response && error.response.status === 403) {
store.dispatch(AuthActions.initState(false, null));
return;
}
throw error;
});
}
if (!isAuthenticated()) {
// => if client side fetch failed with 403, isAuthenticated() turns off to false
// register logined action for client side login succeeded
const reloadAction = TemporaryStoryActions.fetchTemporaryStories({
rowsPerPage: rowsPerPage || 15,
pageIndex: pageIndex || 0,
});
store.dispatch(AuthActions.addLoginedAction(reloadAction));
}
return {
...store.getState(),
} } }
export const withLogin = Page => class SecurePage extends React.Component {
static async getInitialProps (ctx) {
if (ctx.req && ctx.store) {
// server side
const isAuthenticated = ctx.req.isAuthenticated();
const { user } = ctx.req;
ctx.store.dispatch(AuthActions.initState(isAuthenticated, user));
}
return Page.getInitialProps && await Page.getInitialProps(ctx)
}
render () {
const { auth } = this.props;
return auth.isAuthenticated ? <Page {...this.props} /> : <LoginPage />
} }
// when [front-end server] => [api server]
// on Server Side Rendering,
// needs to proxy Cookies which sent to Next.js page request
export const withCookieProxy = (req, targetApi) => {
if (!req) {
return targetApi;
}
targetApi.client.interceptors.request.use(config => {
const cookieString = Object.keys(req.cookies).map(key => `${key}=${req.cookies[key]}`).join('; ');
const headers = {
...(config.headers || {}),
Cookie: cookieString,
};
return {
...config,
headers: headers,
};
}, error => {
return Promise.reject(error);
});
return targetApi;
};
const loginEpic = (action$, state$) => action$.pipe(
ofType(AuthActionTypes.login),
mergeMap(action => {
const email = action.payload.email;
const password = action.payload.password;
return from(AuthApi.login(email, password))
.mergeMap(json => {
const user = json.user;
const loginedActions = state$.value.auth.loginedActions;
const successActions = [
AuthActions.removeAllLoginedActions(),
...loginedActions,
AuthActions.loginSuccess(user.id, user.name, user.last_login_date),
];
return from(successActions);
}).pipe(catchError(error => {
return of$(AuthActions.loginFail(error));
}));
}));
当一些简单的事情可以做时,似乎很复杂,例如:
export const withAuthSync = WrappedComponent => class extends Component {
componentDidMount() {
window.addEventListener('storage', this.syncLogout);
}
componentWillUnmount() {
window.removeEventListener('storage', this.syncLogout);
window.localStorage.removeItem('logout');
}
syncLogout = (event) => {
if (event.key === 'logout') {
console.log('logged out from storage!');
window.location.reload();
}
};
render() {
return <WrappedComponent {...this.props} />;
}
};
此示例已合并为 Next.js 8 的一部分
https://github.com/zeit/next.js/tree/canary/examples/with-cookie-auth
@timneutkens感谢您的链接。
看着https://github.com/zeit/next.js/blob/canary/examples/with-cookie-auth/www/utils/auth.js#L26 -L34 ......不应该有某种在调用auth()
后检查?
在没有 cookie 的情况下测试示例会导致Profile.getInitialProps()
被调用,而我认为重定向会在尝试获得更多“初始道具”之前发生......
我在这里做了一个例子,它有服务器端预渲染+带有阿波罗的身份验证
请记住, OWASP 安全指南建议不要将 JWT 令牌存储在本地存储中,即“单个跨站点脚本可用于窃取这些对象中的所有数据,因此再次建议不要将敏感信息存储在本地存储中。”
这是Auth0:在哪里存储令牌和Tom Abbott:在哪里存储您的 JWT – Cookie 与 HTML5 Web 存储。
这是 Nuxt.js + Express.js 代理服务器 + Django 后端的示例。 当使用存储在 cookie 中的 JWT 令牌时,Express 服务器用于将身份验证请求代理到实际后端并处理 CSRF 保护(对令牌的长度/可以在 JWT 令牌中存储多少信息施加一些限制): https:/ /github.com/danjac/nuxt-python-secure-example
@timneutkens我需要一些关于如何将令牌从 cookie 🍪 发送到 SSR 自定义 redux 中间件的文档。 我正在 _app.js 中获取 cookie。 但是我应该如何将它传递给 customApimiddleware。 我在哪里写过获取请求。 谢谢
我写了一篇关于 Next.js 身份验证/用户帐户的 Medium 文章。
这是一个广泛的教程,是我近两年业余开发和思考的脑残(我对这个问题的第一次评论是从 2017 年 2 月开始的)。https://medium.com/the-ideal-system/user-accounts-with-next-js-an-extensive-tutorial-6831cdaed16b
我认为这是在 nextj.js 应用程序中处理身份验证的最佳教程之一。 我见过诸如将令牌存储到 localStorage (XSS)、将令牌存储在 cookie 中(不处理 CSRF),甚至将令牌存储在来自浏览器的 cookie 中(XSS 和 CSRF 都容易受到攻击)之类的事情。
我真的很喜欢你的反向代理解决方案,并在不同服务之间共享会话信息。 我真的不想为 next.js 应用程序创建自定义服务器,但我认为这是处理会话和防止 csrf(可能添加反向代理)的最直接方法。 我什至可能最终创建了一个整体项目(用于渲染应用程序和处理数据库操作等)。
我已经看到有些人(包括 ZEIT)保持 API 无状态,并让 next.js 应用程序来处理会话。 它将令牌传递给 API。 但是参加会议只会让事情变得更紧凑和更简单。
为 next.js 提供完整的身份验证示例会更好。 诸如外部 API 的身份验证、将会话保留在 next.js 应用程序中、在服务之间共享会话或将令牌传递给它们,甚至可能在令牌过期时刷新令牌。 (很多人写了很多关于 JWT 的文章,只是在他们的教程中使用它们,但他们大多甚至不会过期或什至不刷新它们。)
无论如何,您已经编写了关于该主题的最完整的教程之一。 那谢谢啦!
我真的希望会有更完整和清晰的示例和文档。
为 next.js 提供完整的身份验证示例会更好。 诸如外部 API 的身份验证、将会话保留在 next.js 应用程序中、在服务之间共享会话或将令牌传递给它们,甚至可能在令牌过期时刷新令牌。 (很多人写了很多关于 JWT 的文章,只是在他们的教程中使用它们,但他们大多甚至不会过期或什至不刷新它们。)
我也不知该选择哪种方法。
非常感谢您提供这篇文章的链接。
目前,您选择了哪些实现方式?
你找到下一个v9.3+的全面授权示例了吗?
值得一看 Auth0 基于 cookie 的新方法
(当然,这是针对特定身份提供者的,但该方法可能很有用)
https://github.com/auth0/nextjs-auth0
请记住他们在自述文件中说这种方法是“实验性的”
值得一看 Auth0 基于 cookie 的新方法
(当然,这是针对特定身份提供者的,但该方法可能很有用)
https://github.com/auth0/nextjs-auth0
- 真的很酷,您可以通过 nextjs 的 api 路由(甚至通过一个动态路由)“代理” api 请求
- 然后你永远不必向客户端公开访问令牌等(因为 nextjs api 路由只执行服务器端),服务器端可以通过 auth0 库和带有秘密的 cookie 获取访问令牌等
- 您的客户端代码将调用您的 nextjs api 路由,然后 api 路由将执行真正的 api 请求
请记住他们在自述文件中说这种方法是“实验性的”
这篇文章非常有帮助,它涵盖了许多不同的架构。
https://auth0.com/blog/ultimate-guide-nextjs-authentication-auth0/
使用 API 路由作为代理,通过 API 路由登录/注销,从 API 获取令牌,将其设置为 HttpOnly cookie 我认为是一种可靠的方法。
一个问题可能是 CSRF,但您可以使用csrf
npm 包轻松创建一些解决方案(不是csurf
,但这也可能有效)。
@onderonur ,感谢您的 auth0 文章。
也就是说,目前有一个可靠的或至少是使用 next.js 在纯 jwt 上实现的最小示例?
我不想使用 cookie 制作高级图层并进行设置。 在 csr 应用程序中,我们只是将令牌存储在 localstorage 中并将其与请求一起发送。
@onderonur ,感谢您的 auth0 文章。
也就是说,目前有一个可靠的或至少是使用 next.js 在纯 jwt 上实现的最小示例?
我不想使用 cookie 制作高级图层并进行设置。 在 csr 应用程序中,我们只是将令牌存储在 localstorage 中并将其与请求一起发送。
我已经将这种方法用于我的一个存储库,但它仍处于草稿中,所以一定要自己测试它们:)
https://github.com/onderonur/post-gallery
其实“cookie层”并不是什么高级东西。 只需通过/api/login
API 路由调用 API 的登录端点,如果请求成功,则在httpOnly
cookie 中设置令牌。
您可以检查我的 repo 以获取完全相同的实现。
另一种选择是(如果您希望与在本地存储中设置令牌的流程几乎相同),您可以使用js-cookie
npm 包,使用客户端请求调用您的登录端点,如果返回则结束一个令牌,将其设置在 cookie 中。 当您发出请求时(通过 axios 拦截器等)读取 cookie 值并将其作为请求标头传递给您的 API。 我已经看到很多(甚至一些流行的)应用程序使用这种方法。 但这有点不安全。 因为您不能在浏览器中设置httpOnly
cookie。 因此,JavaScript 将能够读取您的令牌 cookie。 这样,就会出现XSS漏洞。
感谢这是一个旧线程(通常是一个长期运行的主题),但对于那些寻找其他参考或示例的人,我们最近在 NextAuth.js v2 上进行了工作。 我提到它并不是一个插件——它是一个开源项目,很多人已经帮助了它——但它使用起来非常简单,代码和方法可能对人们有用。
对于某些背景,例如 NextAuth v1,它使用签名、带前缀和仅 HTTP 的 cookie,避免使用客户端令牌的常见安全陷阱。
NextAuth.js v2 支持使用 Apple、Google、Facebook、Twitter、GitHub、Auth0、Okta、Slack、Discord 和其他 OAuth 提供商(它同时支持 1.x 和 2.x)登录。 您可以将它与 MySQL、MariaDB、Postgres、MongoDB 一起使用 - 或者不使用数据库(对于 100% 无服务器解决方案,仅使用 OAuth 和 JSON Web 令牌)。
用法非常简单,有一个名为session()
的通用静态方法和一个名为useSession()
的 React Hook,您可以在组件客户端使用:
import { useSession } from 'next-auth/client'
export default () => {
const [session, loading] = useSession()
return <>
{!loading && session && <p>Signed in as {session.user.name || session.user.email}.</p>}
{!loading && !session && <p><a href="/api/auth/signin">Sign in here</a></p>}
</>
}
它是为 Next.js 9.x 和 Serverless 构建的,没有像 Express 或 PassportJS 这样的依赖项。 它包括一个身份验证提供程序,您可以在_app.js
使用它来自动向所有页面添加身份验证状态; 它适用于客户端和服务器端渲染。
有关更多信息,请参阅next-auth.js.org或查看 NPM 上的next-auth@beta
它仍在进行中 - 我们仍在完善文档和事件模型 - 目标发布日期为~月初~6 月中旬。
感谢这是一个旧线程(通常是一个长期运行的主题),但对于那些寻找其他参考或示例的人,我们最近在 NextAuth.js v2 上进行了工作。 我提到它并不是一个插件——它是一个开源项目,很多人已经帮助了它——但它使用起来非常简单,代码和方法可能对人们有用。
对于某些背景,例如 NextAuth v1,它使用签名、带前缀和仅 HTTP 的 cookie,避免使用客户端令牌的常见安全陷阱。
NextAuth.js v2 支持使用 Apple、Google、Facebook、Twitter、GitHub、Auth0、Okta、Slack、Discord 和其他 OAuth 提供商(它同时支持 1.x 和 2.x)登录。 您可以将它与 MySQL、MariaDB、Postgres、MongoDB 一起使用 - 或者不使用数据库(对于 100% 无服务器解决方案,仅使用 OAuth 和 JSON Web 令牌)。
用法非常简单,有一个名为
session()
的通用静态方法和一个名为useSession()
的 React Hook,您可以在组件客户端使用:import { useSession } from 'next-auth/client' export default () => { const [session, loading] = useSession() return <> {!loading && session && <p>Signed in as {session.user.name || session.user.email}.</p>} {!loading && !session && <p><a href="/api/auth/signin">Sign in here</a></p>} </> }
它是为 Next.js 9.x 和 Serverless 构建的,没有像 Express 或 PassportJS 这样的依赖项。 它包括一个身份验证提供程序,您可以在
_app.js
使用它来自动向所有页面添加身份验证状态; 它适用于客户端和服务器端渲染。有关更多信息,请参阅next-auth.js.org或查看 NPM 上的next-auth@beta
它仍在进行中 - 我们仍在完善文档和事件模型 - 目标发布日期为~月初~6 月中旬。
干得好!
这可以单独在客户端使用吗? 例如,我有一个 rails API 应用程序 - 并在客户端使用 next JS。
最有用的评论
所以我有 auth 工作游泳。 正如其他地方提到的,它只是客户端,这最终只是成功的一半。
“很安全”
和php一样,Next的原子单位是页面。 最酷的功能之一是它仅在请求时才延迟加载每个页面。 仅使用客户端身份验证但使用服务器呈现,该受保护页面的 js 实际上是由浏览器下载的。 将来当 Next 添加服务器工作流时,您希望能够在服务器上阻止渲染和重定向以完全防止这种情况。 这将需要 cookie、会话和 AFAIK 会话存储,但这只是执行此类混合应用程序的成本。
身份验证示例
假设您有一个受 JWT 保护的 API,其中包含两个感兴趣的端点:
/token
和/me
。/token
接受电子邮件/密码凭据并返回签名的 JWT (id_token
),而/me
返回与 JWT 身份验证用户相关的个人资料信息。 我改编了以下AuthService.js
来自 Auth0 的锁(删除事件发射器,虽然这不是最糟糕的主意)。 它提取了几乎所有的 JWT 令牌处理,因此可以在登录页面和高阶组件中使用(稍后会详细介绍)。接下来是使保护页面更简单的 HOC。 为了防止不必要的敏感信息闪现,页面将在第一次渲染时服务器渲染
Loading...
,而 react 启动/从 localStorage 读取令牌。 这意味着受保护的页面不会进行SEO,目前这可能还可以,但绝对不是最佳的。现在登录页面不能使用 HOC,因为登录需要公开。 所以它只是直接创建了一个 AuthService 的实例。 您也可以为注册页面做类似的事情。
受到 Airbnb 的 react-with-styles 的启发,我还开始研究
next-with-auth
库,它是一个函数,返回一个用于页面的 HOC。 我还尝试合并AuthService
和这个 HOC。 一种解决方案可能是让这个 HOC 接受一个权限级别的函数作为除了组件之外的参数,比如 redux connect。 无论如何,在我看来,你会像这样使用next-with-auth
:使用 Redux 完成这一切似乎不必要地复杂,但基本上您可以按照 wiki 示例进行操作,但将 AuthService 移至 Actions(登录和注销)并拥有一个 User Reducer。 但是,您只能在客户端上调用这些操作,因为服务器上没有 localStorage,因此您需要在 Actions 中检查它。 最终,redux store 无论如何都放在
window
。 因此,您可以自己将用户缓存在window
上,而不是使用上下文。 如果你不想要 redux,你也可以试试react-broadcast
。最后,假设
next/server
根据 #25 发货。next-with-auth
可以通过中间件 + HOC 将复杂的 localStorage 与 cookie 的东西从开发人员那里抽象出来。 它还可以处理令牌刷新。