Sébastien
Sébastien

Reputation: 103

express-jwt and Angular 9 with cookie: token not set in the `Authorization` header

Well hello there,

I'm working on implementing JWT between my express backend and an Angular 9 frontend.

I have decided to go with a cookie (Which I will secure later).

Using express-jwt, I'm able to create a cookie and to send it to my angular app, with the following code:

// The middleware function
const checkIfAuthenticated = expressJwt({
   secret: RSA_PUBLIC_KEY
})

[...]

// The part where I send the cookie to Angular
res.status(200)
   .cookie('SESSIONID', jwtBearerToken, {httpOnly:true, secure:false})
   .send('OK');

[...]

// My testing route, to be tried after the browser got the cookie
app.get('/test', checkIfAuthenticated, (req, res) => {
    console.log(req.cookies)
    res.status(200).json("Authorized!")
})

I can see the cookie using developers' tools (note the secure:false during development), but if I let checkIfAuthenticated activated, I will got the following message:

UnauthorizedError: No authorization token was found

Then, requests made by Angular, contain the token, but not within the Authorization header as described by tutorials (Like that one: https://blog.angular-university.io/angular-jwt-authentication/), instead the token is in a regular cookie.

This is surprising, because regarding express-jwt documentation:

The default behavior of the module is to extract the JWT from the Authorization header

Using Wireshark, I've observed while calling backend, JWT token is being passed as a regular cookie, where express-jwt is expecting to find it into the Authorization header.

I'm diving the web from hours, but I don't how a classic cookie sent via .cookie('SESSIONID', jwtBearerToken, {httpOnly:true, secure:false}) is supposed to be sent within the Authorization header. I was expecting cookie-parser to be in charge of that, but I doubt a lot now.

Any help is welcome, thanks in advance!

Upvotes: 0

Views: 1930

Answers (1)

svnty
svnty

Reputation: 37

I'm not entirely sure how this system is set up to function and how the cookie middleware for JWT works, from the comment section of the angular-univeristy tutorial I can see someone has commented about the authorizaiton header also not working, concluding that

Safari needs an ending slash on the API's for the Authorization Header to be appended...
Where chrome doesn't need the ending slash and can always add API Headers.
I've appended the slash and it's also working on mobile devices now!

I would say if the cookie is causing you a headache, maybe it's not necessary.. Cookies can be nice to store sessions but there are also a lot of laws that govern the privacy and you will need to inform users that the site uses cookies, where as just using a JWT in the 'Authorization' header can accomplish authentication & authorization.

Consider a middleware of the following using express

import express from 'express';
import cors from 'cors';
import jwt from 'jsonwebtoken';

const app = express();
const JWT_SECRET = 'Your Secret';

app.use(express.urlencoded({ extended: true }));
app.use(express.json());
app.set('trust proxy', true);
app.disable('x-powered-by');
app.use(cors());

// This can throw an error so always catch it!
function decodeJWT(token) {
  return new Promise((resolve, reject) => {
    jwt.verify(token, JWT_SECRET, (err, obj) => {
      if (err) return reject(err);
      return resolve(obj);
    });
  });
}

function encodeJWT(payload, options = { expiresIn: '30d' }) {
  return new Promise((resolve, reject) => {
    jwt.sign(payload, JWT_SECRET, options, (err, token) => {
      if (err) return reject(err);
      return resolve(token);
    });
  });
}

// This is a middleware function that will activate
// from every request that is sent to the server
// The Angular application can read and write to the 'Authorization' header
app.use(async(req, res, next) => {
  const JWT_TOKEN = req.header('Authorization');
  if (JWT_TOKEN) {
    try {
      // This is the custom property we add to the request
      // Adding this property we can check if the JWT token exists
      // in other API end points of our Express application
      // if this verification does not throw an error, it is valid
      // otherwise, the token will not exist in other API points
      req.JWT = await decodeJWT(JWT_TOKEN);
      res.setHeader('Authroization', JWT_TOKEN)
    } catch (err) {
      console.error(err);
      delete req.JWT;
      res.removeHeader('Authorization');
    }
  }
  next();
});

// Declare all rest API below the middleware
app.post('/api/login', async(req, res, next) => {
  const email = req.body.email;
  const password = req.body.password;
  if (email && password) { // Custom validation can be implemented here
    try {
      const JWT_TOKEN = await encodeJWT({ email: email, auth: true });
      res.setHeader('Authorization', JWT_TOKEN);
      return res.status(200).json(JWT_TOKEN);
    } catch (err) {
      return res.sendStatus(500).json(err);
    }
  }
  res.sendStatus(401).send("Email or Password invalid!"); 
});

app.get('/api/members/only', (req, res, next) => {
  if (req.JWT) { // Or if req.JWT.auth === true
    return res.status(200).send('Hello world!');
  }
  return res.status(401).send('Access Denied');
});

app.listen(8080, () => conosle.log('app listening'));

And in your angular application, you may post to the /api/login route with an email and password and receive the JWT token to set in the 'Authorization' header.

An Authroization service in angular may look like this

@Injectable()
export class AuthService {

    constructor(private http: HttpClient) {
    }

    async login(email: string, password: string) {
      let JWT = await this.http.post('/api/login', {email, password});
      localStorage.set('Authorization', JWT);
      return JWT;
    }

    logout() {
      return localStorage.removeItem('Authorization');
    }

    membersArea() {
      // Also consider a HTTP Interceptor instead of manually setting the headers
      let headers = new Headers();
      headers.append('Content-Type', 'application/json');
      headers.append('Authorization', localStorage.getItem('Authorization'));

      let options = new RequestOptions({ headers: headers });
      return this.http.get('/api/members/only', options);
    }
}

Hope it helps

Upvotes: -1

Related Questions