How to provide an interface using a JSON web token after server authentication?

So far, I have dealt only with applications with a server, where after a user logs in through a username / password or using an OAuth provider (Facebook, etc.), the server simply sets a session cookie when redirected to the corresponding page.

However, now I'm trying to create an application using a more “modern” approach, with React on the interface and a JSON API backend. Apparently the standard choice for this is to use a JSON web token for authentication, however, I am having trouble developing how I should provide the JWT to the client so that it can be stored in session / local storage or anywhere else.

An example to illustrate is better:

  • User clicks the link ( /auth/facebook ) to login via Facebook

  • The user is redirected and displayed in the Facebook login form and / or in the permission dialog (if necessary)

  • Facebook redirects the user to /auth/facebook/callback with the authorization code in tow, the server exchanges it for an access token and some user information

  • The server finds or creates a user in the database using the information, then creates a JWT containing the corresponding subset of user data (for example, ID)

  • ???

At this point, I just want the user to be redirected to the main page of the React application (let /app ) with the JWT in tow, so the interface can get the upper hand. But I can’t come up with a (elegant) way to do this without losing the JWT on this path, except to put it in the query string for redirection ( /app?authtoken=... ) - but this will be displayed in the address bar until for now I delete it manually using replaceState() or something else and it seems a little strange to me.

Actually, I’m just wondering how this is usually done, and I’m pretty sure that something is missing here. Node server (Koa with passport), if that helps.

Change To be clear, I ask what is the best way to provide a token to the client (so that it can be saved) after an OAuth redirect stream using a passport.

+6
source share
4 answers

I recently ran into this problem and did not find a solution here or anywhere else, wrote this blog post with my detailed thought.

TL DR: I came up with three possible approaches for sending JWT to the client after OAuth login / redirect:

  • Save the JWT in a cookie, then retrieve it on the interface or server in the next step (for example, retrieve it on the client using JS or send a request to the server, the server uses a cookie to receive JWT, returns JWT).
  • Send the JWT back as part of the query string (which you suggest in your question).
  • Send an HTML page with an HTML tag with a <script> tag that:
    • Automatically saves embedded JWT to localStorage
    • Automatically redirects the client to any page that you like after that.

(Since logging in with JWT is essentially equivalent to “saving JWT to localStorage , my favorite option was # 3, but there may be flaws that I didn’t consider. I am interested to know what others think here.)

Hope this helps!

+2
source

When you receive a token from passport authentication sites, you must save the token in your localStorage browser. A dispatcher is Redux middleware. Ignore dispatch if you are not using shorthand in your application. you can just use setState here (a bit strange without shorthand).

client side:

Here's something like an API that returns a token.

saving tokens

 axios.post(`${ROOT_URL}/api/signin`, { email, password }) .then(response => { dispatch({ type: AUTH_USER }); //setting state (Redux 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 Style) }); 

So, when you make some authenticated requests, you need to attach a token with the request in this form.

authenticated requests

 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); }); 

Here's my index.js: Token is checked every time, so even if the browser is updated, you can still set the state.

authenticates user

 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 another component, checks if a user is authenticated. if not pushes to the index route </Route> </Router> </Provider> , document.querySelector('.container')); 

All actions performed for sending establish a state.

my reducer file (Redux only), otherwise you can just use setState () in the index route file to provide state for the entire application. Each time a submission is called, it launches a similar reducer file, similar to this one, which sets the state.

state setting

 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 

Remove the token from the local storage to exit the system.

Warning: Use any other name, not token , to save the token in the localStorage browser

server side:

tailored to your passport file. You must set the title search. Here 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 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); 

In my 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); 
+1
source

here is a request to enter from the server. it saves the token in the header:

 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(); }); }); 

here is the login request on the reaction side, where I grab the token from the header and set the token in local storage after authentication of the username and password:

 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'); } }) } 
0
source
  • Customer. Open a popup through $ auth.authenticate ("vendor name").
  • Customer. Log in with this provider, if necessary, then authorize the application.
  • Client: after successful authorization, a pop-up window is redirected back to your application, for example. http: // localhost: 3000 with the query string code (authorization code).
  • Client: The code parameter is sent back to the parent window, which opens a pop-up window.
  • Client: the parent window closes the pop-up window and sends a POST request to the / auth / provider parameter with the code.
  • Server: authorization code is exchanged for an access token.
  • Server: user information is retrieved using the access token from step 6.
  • Server. Look at the user by their unique provider ID. If the user already exists, take the existing user, otherwise create a new user account.
  • Server: in both cases, in step 8, create a JSON web token and send it to the client.
  • Client: analyze the token and save it in local storage for later use after reloading the page.

    Sign Out

  • Client: remove token from local storage
0
source

Source: https://habr.com/ru/post/1014640/


All Articles