When I was making authentification cookies I couldn't find clear help for both client and server side. So to prevent you from wasting time like me, I'm making this article :
Login
1. Client side request
This fetch request send the information typed by the user to verify if the name and the password are correct and receive a response back which is the JWT cookie.
const response = await fetch("http://127.0.0.1:8080/user/signin", {
method: "POST",
credentials: "include",
headers: {
"content-type": "application/json",
},
body: JSON.stringify({
pseudo: pseudo,
password: password,
}),
})
.then((res) => res.json())
.then((data) => {
console.log(data);
});
I've seen a lot of people say "my cookie is working only on postman but not on my local server". The answer to this problem is the CORS (Cross-origin resource sharing) options.
The important part here is credentials: "include",
it allows you to send cookies even if the URL or the port of the request is different from the response one. In opposite to "same-origin" which is the default value.
2. CORS options
But for it to work you also need to set two CORS options :
app.use(
cors({
origin: ["http://127.0.0.1:8080", "http://127.0.0.1:5500"],
credentials: true,
})
);
origin : By default, pages with different URL cannot access to each other. Using origin: ["http://127.0.0.1:8080", "http://127.0.0.1:5500"],
will add the two host URL to the Access-Control-Allow-Origin header allowing you to make request between them.
credentials : As i said, by default CORS does not include cookies on cross-origin requests so they can only go to origins from which they came.
3. Controller
We now make our controller that check if the user information is correct, create the JWT token with the user id and create the cookie with the JWT token.
const JWT_MAX_AGE = 1000 * 60 * 60 * 24 * 30; // 30 days in ms
router.post("/login", async (req, res) => {
if (!req.body.pseudo) return res.status(400).send({ ok: false, error: "Please provide a pseudo" });
if (!req.body.password) return res.status(400).send({ ok: false, error: "Please provide a password" });
const user = await UserObject.findOne({ pseudo: req.body.pseudo });
if (!user) return res.status(400).send({ ok: false, error: "User does not exist" });
if (req.body.password !== user.password) return res.status(400).send({ ok: false, error: "Authentification is incorrect" });
// create a JWT token with the user id
const token = jwt.sign({ _id: user._id }, "your-secret-key", { expiresIn: JWT_MAX_AGE });
// create a cookie with the jwt token
res.cookie("jwt", token, { maxAge: JWT_MAX_AGE, httpOnly: true, secure: true });
return res.status(200).send({ ok: true, token: "JWT " + token });
});
Make sure to store your token secret key in a secure file (.env).
You can set some options to your cookies to make it more secure against XSS attacks for exemple :
httpOnly : Flags the cookie to be accessible only by the web server and not via JavaScript in the browser.
secure : Marks the cookie to be used only by HTTPS protocol and not by HTTP. (except on localhost)
maxAge : option for setting the expiry time relative to the current time in milliseconds.
Display user informations
1. Passport
Once you're logged in, we want to manage route authorization. So we need to get the value of the desired cookie and decrypt the JWT token to get the user id. To do this we will need Passport-JWT strategy, which has also the nice feature to add the DB user in the request object, so that it's available in the controller afterwards.
const passport = require("passport");
const config = require("./config");
const JwtStrategy = require("passport-jwt").Strategy;
// load up the user model
const User = require("./models/user");
const cookieExtractor = function (req) {
let token = null;
if (req && req.cookies) token = req.cookies["jwt"];
return token;
// return the value of the cookie named jwt
};
module.exports = (app) => {
passport.use(
"user",
new JwtStrategy(
{
jwtFromRequest: cookieExtractor, // JWT token value
secretOrKey: "your-secret-key",
},
async function (jwtPayload, done) {
try {
const user = await User.findById(jwtPayload._id);
if (user) return done(null, user);
} catch (e) {
console.log("error passport", e);
}
return done(null, false);
}
)
);
app.use(passport.initialize());
};
If the token is properly decrypted and not expired, we will try to get the user from the database, and if a user exists, passport will add this user in the request object.
Otherwise, passport will reject the request by sending something like res.status(401).send({ ok: false, error: 'Unauthorized' })
2. Controller
And the result route to display the user informations
router.get(
"/result",
passport.authenticate("user", { session: false }),
catchErrors(async (req, res) => {
console.log(req.user, "Identified user");
res.status(200).send({ ok: true, data: req.user });
})
);
Logout
1. Client side request
We can now make our Logout route.
const response = await fetch("http://127.0.0.1:8080/user/logout", {
method: "GET",
credentials: "include",
})
.then((res) => res.json())
.then((data) => {
console.log(data);
});
This fetch function load our logout route and clear our cookie.
2. Controller
router.get(
"/logout",
catchErrors(async (req, res) => {
// delete the cookie with the name jwt
res.clearCookie("jwt", {});
res.status(200).send({ message: "Successfully logged out" });
})
);
Make sure to secure your CORS options before sending to production. You can easily find good articles about that.
You can find all the files in my github repository
Hope this helped.
Top comments (0)