如何在服务器身份validation后使用JSON Web令牌提供前端?
到目前为止,我只处理服务器呈现的应用程序,在用户通过用户名/密码login或使用OAuth提供程序(Facebook等)后,服务器只是在redirect到相关页面时设置会话cookie。
然而,现在我试图用一种更现代的方法构build一个应用程序,在前端和一个JSON API后端上都有React。 显然这个标准的select是使用JSON Web令牌进行身份validation,但是我很难解决如何为客户端提供JWT,因此可以将其存储在会话/本地存储或任何地方。
举例说明更好:
-
用户点击链接(
/auth/facebook
)通过Facebooklogin -
用户被redirect并显示Facebooklogin表单和/或权限对话框(如有必要)
-
Facebook将用户redirect到
/auth/facebook/callback
,授权码被拖拽,服务器通过访问令牌和一些关于用户的信息进行交换 -
服务器使用info在数据库中查找或创build用户,然后创build一个包含用户数据相关子集(如ID)的JWT,
-
???
在这一点上,我只是希望用户被redirect到React应用程序的主页(比方说/app
),因此前端可以接pipe。 但我不能想到一个(优雅)的方式来做到这一点,而不会丢失JWT,除了把它放在redirect的查询string中( /app?authtoken=...
) – 但是会显示在地址栏中,直到我用replaceState()
或者其他方法手动删除它,并且对我来说似乎replaceState()
。
真的,我只是想知道这是如何做的,我几乎可以肯定我在这里失去了一些东西。 服务器是节点(Koa与护照),如果有帮助。
编辑:为了清楚,我问了什么是使用Passport OAuthredirectstream后 ,提供一个令牌给客户端(这样可以保存)的最佳方法。
从任何护照authentication网站获取令牌时,必须将令牌保存在浏览器的localStorage
。 Dispatch是Redux的中间件。 如果您不在应用程序中使用redux,请忽略dispatch
。 你可以在这里使用setState
(有点奇怪,没有redux)。
客户端:
这是我的类似的API,它返回的令牌。
保存令牌
axios.post(`${ROOT_URL}/api/signin`, { email, password }) .then(response => { dispatch({ type: AUTH_USER }); //setting state (Redux's Style) localStorage.setItem('token', response.data.token); //saving token browserHistory.push('/home'); //pushes back the user after storing token }) .catch(error => { var ERROR_DATA; try{ ERROR_DATA = JSON.parse(error.response.request.response).error; } catch(error) { ERROR_DATA = 'SOMETHING WENT WRONG'; } dispatch(authError(ERROR_DATA)); //throw error (Redux's Style) });
所以当你进行一些authentication的请求时,你必须在这个表单中附上带有请求的标记。
authentication请求
axios.get(`${ROOT_URL}/api/blog/${blogId}`, { headers: { authorization: localStorage.getItem('token') } //take the token from localStorage and put it on headers ('authorization is my own header') }) .then(response => { dispatch({ type: FETCH_BLOG, payload: response.data }); }) .catch(error => { console.log(error); });
这是我的index.js:每次都检查令牌,所以即使浏览器刷新了,仍然可以设置状态。
检查用户是否被authentication
const token = localStorage.getItem('token'); if (token) { store.dispatch({ type: AUTH_USER }) } ReactDOM.render( <Provider store={store}> <Router history={browserHistory}> <Route path="/" component={App}> .. .. .. <Route path="/blog/:blogid" component={RequireAuth(Blog)} /> //ignore this requireAuth - that's another component, checks if a user is authenticated. if not pushes to the index route </Route> </Router> </Provider> , document.querySelector('.container'));
所有这些动作都是为了设置状态。
我的reducer文件(只有Redux),否则你可以在你的索引path文件中使用setState()来为整个应用程序提供状态。 每次调用调用时,都会运行一个类似的设置状态的reducer文件。
设置状态
import { AUTH_USER, UNAUTH_USER, AUTH_ERROR } from '../actions/types'; export default function(state = {}, action) { switch(action.type) { case AUTH_USER: return { ...state, error: '', authenticated: true }; case UNAUTH_USER: return { ...state, error: '', authenticated: false }; case AUTH_ERROR: return { ...state, error: action.payload }; } return state; } //you can skip this and use setState() in your index route instead
从localStorage中删除令牌来注销。
警告:使用任何不同的名称而不是token
将令牌保存在浏览器的localStorage
服务器端:
考虑你的护照服务档案。 您必须设置标题search。 这里是passport.js
const passport = require('passport'); const ExtractJwt = require('passport-jwt').ExtractJwt; const JwtStrategy = require('passport-jwt').Strategy; .. .. .. .. const jwtOptions = { jwtFromRequest: ExtractJwt.fromHeader('authorization'), //client's side must specify this header secretOrKey: config.secret }; const JWTVerify = new JwtStrategy(jwtOptions, (payload, done) => { User.findById(payload._id, (err, user) => { if (err) { done(err, null); } if (user) { done(null, user); } else { done(null, false); } }); }); passport.use(JWTVerify);
在我的router.js
const passportService = require('./services/passport'); const requireAuthentication = passport.authenticate('jwt', { session: false }); .. .. .. //for example the api router the above react action used app.get('/api/blog/:blogId', requireAuthentication, BlogController.getBlog);
这里是来自服务器端的login请求。 它将标记存储在标题中:
router.post('/api/users/login', function (req, res) { var body = _.pick(req.body, 'username', 'password'); var userInfo; models.User.authenticate(body).then(function (user) { var token = user.generateToken('authentication'); userInfo = user; return models.Token.create({ token: token }); }).then(function (tokenInstance) { res.header('Auth', tokenInstance.get('token')).json(userInfo.toPublicJSON()); }).catch(function () { res.status(401).send(); }); });
这里是反应端的login请求,在这里我从标题中获取令牌,并在用户名和密码通过authentication时将令牌设置在本地存储中:
handleNewData (creds) { const { authenticated } = this.state; const loginUser = { username: creds.username, password: creds.password } fetch('/api/users/login', { method: 'post', body: JSON.stringify(loginUser), headers: { 'Authorization': 'Basic'+btoa('username:password'), 'content-type': 'application/json', 'accept': 'application/json' }, credentials: 'include' }).then((response) => { if (response.statusText === "OK"){ localStorage.setItem('token', response.headers.get('Auth')); browserHistory.push('route'); response.json(); } else { alert ('Incorrect Login Credentials'); } }) }
- 节点+快递+护照:req.user未定义,但在邮递员的作品
- Firebase 3.3.x Nodejs – createUserWithEmailAndPassword不是一个函数
- Nodejs / mongodb-检查用户是否具有pipe理员权限(基于令牌的身份validation)
- nodejs – 证书链中的错误自签名证书
- Sails.js 0.11和护照:“Missing Credentials”错误
- 套接字build立后的socket.ioauthentication
- req.session.passport和req.user空白,req.isAuthenticated在初次成功login后使用passport-facebook返回false
- 麻烦authentication
- LocalStrategy和ClientPasswordStrategy之间的护照区别