How to send JWT to React client from a callback?

All we need is an easy explanation of the problem, so here it is.

I am trying to create an application with passwordless authentication, with social medias like Facebook and by mail.

I’m stupidly stuck a one point, maybe I have not understand something.

Lets name my project MyApp:

If a user try to log to facebook from myapp.com/, he will be redirected to facebook.com/login, then facebook redirect him to myapp.com/callback.

So, on my server, the route / and /callback are quite the same: they just send my React app to he user, but /callback generate a JWT token.

From here is my problem : How can I send my JWT token to the client in the same time that the React app?

Maybe I have missed something from another provider, but the passwordless principle by mail is the same thing : just a link to authenticate the user, and just a redirection to the /callback, no external provider.

I’m completely lost 🙁

Thank you!

How to solve :

I know you bored from this bug, So we are here to help you! Take a deep breath and look at the explanation of your problem. We have many solutions to this problem, But we recommend you to use the first method because it is tested & true method that will 100% work for you.

Method 1

You can put the JWT into the query string of the URL you want to redirect the user to. Then you get the JWT in the client from accessing the query string, and save it to local storage. I’ve done it myself recently check out my repo server here, client here

Edit: I post the code of the callback route in the server and getting the token in the client for more details check the links to my repo above

Callback Route

app.get('/auth/github/callback', (req,res) => {
githubAuth.code.getToken(req.originalUrl)
    .then(user => {
        console.log(user.data);
        return fetch('https://api.github.com/user?access_token=' + user.accessToken);
    })
    .then(result => {
        return result.json();
    })
    .then(json => {
        //console.log(json);
        //res.send(json);
        return User.findOne({githubId: json.id})
            .then(currentUser => {
                if(currentUser){  // user already exists
                    console.log('User exists with GitHub Login: ' + currentUser);
                    return currentUser;
                }
                else{  // User doesn't exist
                // saved automatically using mongoose, returns Promise
                    return new User({
                        username: json.login,
                        githubId: json.id
                    }).save()
                        .then(newUser => {
                            console.log('New User created with GitHub Login: ' + newUser);
                            return newUser;
                        });
                }
            });
    })
    .then(user => {
        // Now use user data to create a jwt
        const token = jwt.sign({id: user._id}, process.env.JWT_ENCRYPTION_KEY, {
            expiresIn: 86400  // expires in 24 hours
        });

        const encodedToken = encodeURIComponent(token);
        res.status(200);
        res.redirect('/profile?token=' + encodedToken);
    })
    .catch(err => console.log(err));
});

Token Retrieval in Client

class UserProfile extends React.Component{
    constructor(props){
        super(props);
        this.state = {username: ''};
    }

    componentDidMount(){
        const query = new URLSearchParams(this.props.location.search)

        // When the URL is /the-path?some-key=a-value ...
        let token = query.get('token')
        console.log(token)

        if(token){  // it's the first time we receive the token
            // we save it
            console.log('Saving token');
            localStorage.setItem('userjwt', token);
        }
        else{
            // if qrstring is empty we load the token from storage
            token = localStorage.userjwt;
            console.log('Loading token: ' + token);
        }

        if(token){
            const headers = new Headers({
                'x-access-token': token
            });

            const reqOptions = {
                method: 'GET',
                headers: headers
            };

            fetch('/user', reqOptions)
                .then(res => res.json())
                .then(user => {
                    console.log(user);
                    console.log(user.username);
                    this.setState({username: user.username});
                })
        }
    }

    render(){
        if(this.state.username === ''){
            return(
                <div className="social-profile">
                    <h2>Login first</h2>
                </div>
            );
        }
        else{
            return(
                <div className="social-profile">
                    <h2>User: {this.state.username}</h2>
                </div>
            );
        }
    }
}

Method 2

This is how I do it in my app, note that this is a slight improvement from Dimitris’s answer because of the security concern mentioned by Thomas linked to here, and requires Redis (or database if you prefer it).

Instead of passing the JWT directly on the query parameter, what I did was:

  1. Generate a random token (in my case it was 64 alphanumeric characters)
  2. Store to your Redis with the random token as the key, and jwt as the value with short TTL (I set mine to 1 minute)
redisClient.set(randomToken, jwtToken, "EX", 60);
  1. Pass the token into the query param on redirect URL
res.redirect(`http://example.com/jwt?token=${randomToken}`);
  1. On your client, get the token from query param, and send a HTTP Request to your express route to get JWT from redis with the token
router.get("/getJwt/:token", (req, res, next) => {
    const jwt = redisClient.get(token);
    // delete the JWT from redis
    redisClient.del(token);

    // you can return 401 if JWT doesnt exists on the Redis server
    res.json(jwt);
});
  1. Store jwt in client’s local storage

This requires more work, resource and one extra HTTP request to get the JWT, but I think it’s worth it considering the risk of passing JWT directly on the query param. It’s not like the user will do authentication every minute anyway.

Note: Use and implement method 1 because this method fully tested our system.
Thank you 🙂

All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply