Adding Authentication to Mern App (In Progress)

Adding Authentication to Mern App (In Progress)

This is another simple guide that I am putting together for my future self.  As I make my latest app from the ground up I want to document my experiences.  This is not meant to be a thorough guide – it’s just a list of steps needed to accomplish the goal.

yarn add jsonwebtoken
yarn add passport passport-jwt passport-local

 

Perform this in the server folder.  This package contains the platform for various authentication methods that can be used.

passport-jwt will handle the protected route secret if the user is authenticated from a provider.  “local” handles the email/password scenario.

const passport = require("passport");
const LocalStrategy = require("passport-local");
const passportJWT = require("passport-jwt");

Create the corresponding variables in your server file (in my case, “/server.js”).

var apiRouter = require('./server/routes/api');
router.use('/api', apiRouter);
app.use(express.static("client/build"));

Put the api routes before the the declaration of the client-side public files (more information on the latter here).

const passport = require("passport");
const LocalStrategy = require("passport-local");
const passportJWT = require("passport-jwt");
JWTStrategy = passportJWT.Strategy;
const app = express();
app.use(passport.initialize());

Initializing the JWT strategy and passport instance.

The jwt token is split into three parts – header, payload and signature.  This is essentially an object encoded as a string.  The signature verifies that your token is legitimate.

const user = {
  id: "1",
  email: "example@example.com",
  password : "password"
}
Create a dummy user for now to keep this simple.  Later on we will check mongodb for a real user.
passport.use(new JWTStrategy({
  jwtFromRequest: passportJWT.ExtractJwt.fromAuthHeaderAsBearerToken(),   // where are we getting JWT token from
  secretOrKey:"jwt_secret"  // make this more complex
}, (jwt_payload, done) => {  // payload coming back
    if (user.id === jwt_payload.user._id){
      return done(null, user)
    } else {
      return done(null, false, {
        message: "the user was not found"
      })
    }
}))

In this step we look to see if the user in the incoming request matches a user.  For now we will compare it to the temporary user we created earlier.  Note that the jwt_secret is defined here – in my prod script this is moved to an environment variable (see here).

passport.use(new LocalStrategy({
  usernameField:"email"
}, (email, password, done) => {  // callback for authentication
    if (email === user.email && password === user.password) {
      return done(null, user);
    } else {
      return done(mull, false)
    }
}))

Here we are handling the email/password scenario.  This is very similar to the previous code block – instead of extracting the JWT token from the authentication header we are comparing the username and password.

 <ThemeProvider theme={theme}>
      <UserProfileProvider>
        <BrowserRouter>
          <Header value={value} setValue={setValue} selectedIndex={selectedIndex} setSelectedIndex={setSelectedIndex} />
          <Switch>
            <Route exact path="/" component = {Dashboard} />
            <Route exact path="/login" component = {SignIn} />

Use browser router to handle client-side routing.

const server = process.env.REACT_APP_API_SERVER;
export const login = (email, password) => {
  return fetch(`${server}/api/login`, {
    method: "POST",
    body: JSON.stringify({
      email,
      password,
    }),
    headers: {
      "Content-Type": "application/json",
    },
  }).then((res) => {
    if (res.status === 200) {
      return res.json().then((data) => {
        localStorage.setItem('token', data.token); // for testing only
        return data.token
      });
    } else {
      const error = new Error(res.error);
      throw error;
    }
  });
};
export const getSecret = () => {};

I created this helper utility “api” to handle the call to the backend server.  It will be used in Login component.

 const history = useHistory();
  const onSubmit = async (event) => {
    event.preventDefault();
    try {
      const token = await login(event.target.email.value, event.target.password.value)
      console.log(token)
      history.push("/");
    } catch (error) {
      console.error(error);
      alert("Error logging in please try again");
    }
  };

From the Login component.  Note that the onSubmit is attached to form in the html.  If successful login then route to the root of the app.

 app.post("/login", (req, res, next) => {
    passport.authenticate("local", (err, user) => {
      if(err){
        return next(err)
      }
      if(!user){
        return res.send("Wrong email or password")
      }
      req.login(user, () => {
        const body = {_id: user.id, email: user.email }
  
        const token = jwt.sign({user: body}, "jwt_secret")
        return res.json({token})
      })
    })(req, res, next)
  })

We now need to account for the api call back to the express server in the api router file.  Note that I left the jwt_secret string in here for reference only – it comes from an environment variable in the real code.

 

 

 

 

 

Add a comment

*Please complete all fields correctly

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Related Blogs