Search code examples
reactjsjwtcookie-httponly

How to extract a JWT generated by Cognito, and store as HTTPonly cookie and use with subsequent API requests (React)


I am using cognito for user authentication. Upon successful authentication (userEmail and password) Cognito generates id, access and refresh tokens which I can see in my console.log responses.

My question is, how can extract these tokens and store them as a 'global scope variable' for use (potentially as a HTTPOnly Cookie) with API calls in various React components in my application?

(I am open to any suggested 'fixes' to my current approach, or equally, sugestions on more efficient ways of managing this): What I have tried..

Defining the JWT token as a variable in a JWTutils.js component and importing it to the login.js where I assign the value of the response from cognito. My thinking is that this would allow me to use the token value throughout the application. All, this does however, is interrupt a successful login. So even when I enter correct details, my console.log actually shows onSuccess, but immediately after my browser renders the cognito Modal saying Failure : Username or password are incorrect.

What is the best way to deal with and manage the JWT from the client side?

My code is attached below: Login.js

import React from 'react'
import poolData from '../Userpool';
import { CognitoUser, AuthenticationDetails } from 'amazon-cognito-identity-js';
import { useState, useEffect } from 'react';
import { useHistory } from 'react-router-dom';
import JWT from '../utils/JWTutils';

const Login = () => {

    const[email,setEmail]=useState("");
    const[password,setPassword]=useState("");
    
    let isAuth = false;

    let history = useHistory();
    
    const onSubmit =(event)=>{
        event.preventDefault();

        const user= new CognitoUser({
            Username: email,
            Pool: poolData,
        } );

        const authDetails = new AuthenticationDetails({
            Username: email,
            Password : password,
        } );

        console.log("authDetails", authDetails)
        console.log("user", user )

        user.authenticateUser(authDetails,{
            onSuccess: (data)=>{
                console.log("On Success: " , data)
                console.log("JWT", data.idToken.jwtToken)
                JWT=data.idToken.jwtToken
                isAuth =true;
                JWT = (data.idToken.jwtToken) // <----this is where the modal 'pops up' and the //application fails
                    history.push("/secure");
            }  ,
            onFailure:(err)=>{
                alert("Failure : Username or password are incorrect")
                history.push("/")
            } ,
            newPasswordRequired: (data)=>{
                console.log("New Password Required: ", data)
            }
        }  )
    }

    return (
        <div>
            <br />
            <hr />
            <h2>Login</h2>
            <form onSubmit={onSubmit} >
            <label htmlFor="email">Email</label>
            <br />
            <input type="email" value={email} 
            onChange={(event)=>setEmail(event.target.value)} />
            <br />
            <label htmlFor="password">Password</label>
            <br />
            <input type="password" value={password}
            onChange={(event=>setPassword(event.target.value))} />
            <br />
             <button>Login</button>
            </form>
        </div>
    )
} 


export default Login;

JWTutils.js

  const JWT =""
export default JWT;

Solution

  • The way you have it, JWT is a const and cannot be reassigned. This is likely why your current approach blows up.

    You could easily convert JWTUtils to a simple "store" that allows getting/setting of the token:

    // JWTUtils
    let token;
    
    export default {
      setToken: t => token = t,
      getToken: () => token,
    }
    

    And then use that in your success handler:

    onSuccess: (data) => {
      JWT.setToken(data.idToken.jwtToken)
      // ...whatever else you need to do...
    }
    

    Anything else that needs it can then access it via getToken:

    import JWT from '../JWTUtils';
    
    const token = JWT.getToken();
    
    if (token) {
      // do whatever you need to do
    }
    

    You could extend the JWTUtil to stash it in local storage so it would persist across page reloads, etc.


    Note: You could just change const JWT = "" to let JWT = "", but this would allow any code anywhere to change its value, which is A Bad Idea™ and could lead to problems that are difficult to diagnose.

    By sending it through a well-defined API (getToken, setToken) and keeping the variable itself private you can enforce rules around how it's accessed, set debugging breakpoints whenever it changes, swap out the actual implementation without affecting existing usage, etc.