Firebase auth user.getIdToken() sending expired tokens - javascript

As far as I know, getIdToken() should by default send an unexpired token to the server, handling the token refreshing internally. However, I am getting many many errors from my server regarding expired tokens.
On my frontend, I created axios instance as follows:
const base = axios.create({
baseURL: apiUrl,
});
base.interceptors.request.use(async (config) => {
const token = await getAccessToken();
config.headers.Authorization = `Bearer ${token}`;
return config;
});
const getAccessToken = async () => {
const user = firebase.auth().currentUser;
if (user) {
const token = await user.getIdToken();
return token;
}
return null;
};
Then on the receiving server, I verify the token by doing:
const admin = require("firebase-admin");
const auths = req.headers.authorization.split(" ");
admin.auth().verifyIdToken(auths[1]).then(async token => {
...
});
The error I receive from the server is:
Error: Firebase ID token has expired. Get a fresh ID token from your client app and try again (auth/id-token-expired). See https://firebase.google.com/docs/auth/admin/verify-id-tokens for details on how to retrieve an ID token.
If I change getIdToken() to getIdToken(true), the issue goes away.
I verified that the getAccessToken method is called for every request made. I'm a bit lost why my server continues to complain about expired tokens, any help would be appreciated.

Related

request to login to a node server, using react

Here is the situation:
I have a database which contains a user and password registered.
My assignment, for now, is to create a login form, and login with a registered uname and pw.
Uname and pw are registered in the server/database already.
ps: I did not create the server nor database.
Node server code
import express from 'express';
import cors from 'cors';
import http from 'http';
import { Sequelize } from 'sequelize';
import { Data } from './database';
import { router } from './routes/Router';
import { initialData } from './database/someData';
const closeServer = async (
server: http.Server,
sequelize: Sequelize,
signal?: string
) => {
server.close();
await sequelize.close();
process.exit();
};
const runServer = async (): Promise<void> => {
const PORT = process.env.PORT || 8082;
const app = express();
const sequelize = Data.init();
app.use(
cors({
credentials: true,
origin: 'http://localhost:3000',
})
);
app.use('/api', router);
const server = app.listen(PORT, () => {
console.log(`Starting server at ${PORT}`);
});
try {
await sequelize.authenticate();
await sequelize.sync({
force: process.env.SERVER === 'reset',
});
if (process.env.SERVER === 'reset') await initialData();
} catch (e) {
closeServer(server, sequelize);
throw e;
}
};
runServer()
.then(() => {
console.log('Run successfully');
})
.catch((ex: Error) => {
console.log('Unable to run:', ex);
});
I need help on what is that I have to do.
When I input username and pw, on the form, what are the methods to use for sending the info?
And then, when the info reaches the server, i think the username and pw need to be validated with jwt, and then check if the user and pw exists. how do i do that?
What i have understood so far is that i gotta use axios to send info to server, but thats it.
Do i need to use jwt for the login?
What is the normal flow for this kind of mechanism?
I am using react as a framework.
So there are quite few steps here.
First you have to create endpoint on your backend server for issuing jwt tokens. Jwt tokens can be used as a pass for user to login. So in your router you would add something like this:
router.post('/login', (req, res)=> {
const username = req.body.username
const password = req.body.password
// Then you make db call to verify username and password are correct.
if Credentials are valid, you would issue jwt token
jwt.sign({
// here you can save extra information of user. Also remember this information must be public since anyone can see it. Do not put user password here
email: 'email',
userId: 'id',
}, "secret")
})
After this, you need some kind of middleware on backend, so that on each user request, you check and verify this jwt token which is sent from react application. For example you could write isAuth middleware:
const jwt =require("jsonwebtoken");
export const isAuth= (req, res, next) => {
try {
// here we attach request in auth header, with Bearer "jwt token" format. So we extract jwt token and verify it
const authHeader = req.get("Authorization");
if (!authHeader) {
return res.status(401).json({ message: "no token" });
}
const token = authHeader.split(" ")[1];
let decodedToken;
decodedToken = jwt.verify(token, "secret");
if (!decodedToken) {
return res.status(401).json({ message: "Wrong token" });
}
req.userId = decodedToken.userId;
next();
} catch (err) {
console.error(err);
return res.status(401).json({ message: err });
}
};
Now you would be able to have backend endpoints like this:
// This is how you would require login on some routes
router.post("/getMyPrivateInfo", isAuth, QueryPrivatInfo)
Now on React side, you would make request for login like this:
axios.post("/login", {
username: '1',
password: "2"
})
This would return jwt token, now you would save this token in local storage.
After its saved in local storage and you make request with axios for private info you would do following
axios.post("/getMyPrivateInfo", {any request body info neeeded}, {
headers: {
Authorization: "Bearer jwtTokenFromLocalStorage"
}
})
This is how whole flow will work, hope it makes sense

Express doesn't set cookies with httpOnly flag

I have my back-end Express.js server that has sign in function. After user sign in, he gets 2 tokens - access token and refresh token. What I want to do, is to make return from server refresh token as httpOnly cookie.
Here is a peace of code of this function:
const { refreshToken, accessToken } = await jwtService.updateTokens({
userId: client.id, username: client.username
}, { transaction })
logger.info(`Client ${client.email} has been successfully signed in!`)
await transaction.commit()
return res
.status(200)
.cookie("refreshToken", JSON.stringify(refreshToken), { httpOnly: true, secure: false })
.json({ accessToken, reopening: reopening ? client.username : null })
Basically, browser just doesn't set this cookie as httpOnly and doesn't set it at all, actually. So, I was trying to ping this endpoint with postman, and it works:
In reponse body I have access token and in httpOnly cookie I have this refresh token.
So, the problem is, why browser doesn't save it? I have found a couple of solutions and all of them were about cors and axios credentials. I use 2 express servers - 1 is for normal back-end and 1 is for front-end.
Here is how "path" from front-end to back-end looks like:
Sign in function that send request to front-end express server:
const api = axios.create({
baseURL: apiUrl,
headers: {
'Content-Type': 'application/json'
}
})
export const signIn = async payload => {
try {
const { data } = await api.post('s-i', payload)
return data
} catch (e) {
return e.response.data
}
}
Front-end express server sends request to actual back-end:
const api = axios.create({
baseURL: process.env.NODE_ENV === "development" ? process.env.PNB_API_DEV : process.env.PNB_API_PROD,
})
const router = Router()
router.post('/s-i', async (req, res) => {
try {
const { data } = await api.post('/sign-in', req.body)
res.json(data)
} catch (e) {
return res.status(e.response.status).json(e.response.data)
}
});
And then that function that was at the very begging.
So - the question is - how to make browser save those httpOnly cookies? If it's really about credentials or cors where should I put those settings?
PS
Back-end port - 3001 and front-end port - 8010.

How do I make node.js using express find a JSON Web Token (JWT) inside of a response header cookie in user's browser?

I have a question on how to search for my JWT token inside of a user's browser cookies.
Below I have some code that searches the user's browser for cookies in the response header, but I am not sure how to make the code more specific and search for the JWT token within the cookie and verify that it is an actual JWT token that was a assigned to that user.
const jwt = require('jsonwebtoken');
const router = require('express')();
const cookieParser = require('cookie-parser');
router.use(cookieParser());
module.exports = function(req,res,next){
const token = req.header('Cookie');
if (!token) {
return res.status(403).send('Access Denied');
}
try{
const verified = req.header('Cookie');
req.user = verified;
// const verified = jwt.verify(token, process.env.TOKEN_SECRET);
// req.user = verified;
next();
} catch (err) {
res.clearHeader;
res.status(403).send('Invalid Token');
}
};
I hope I didn't misunderstand your question and waste a bunch time.
Short Answer: How to retrieve information
Use req.body or req.headers. If something will contain the token or authentication details, then it's one of these two.
Full Auth Walkthrough:
To get the JSON Web Tokens you first have to generate them. Wouldn't recommend implementing your own token authentication though. I'll show how to create a whole authentication system here step by step.
For simplicity, let's say we have an exported route in a file auth.js, this route will be a sub-route domain.com/auth, an array of all active refreshTokens and the jwt:
const express = require("express")
const jwt = require("jsonwebtoken")
let route = (exports.route = express())
let refreshTokens = []
What we will do is generate a long-lasting refresh token, which users will be able to use to generate a smaller 15-minute access token. Afterwards, you generate a new access token with the refresh token and so on. But to get the refresh token you need to login or register. Users can also logout killing the refresh token.
route.post("/token", async (req, res) => {
// Input: Refresh Token
// Output: Access Token Generation
})
route.post("/login", async (req, res) => {
// Input: User, Password
// Output: Refresh Token
})
route.delete("/logout", async (req, res) => {
// Input: Token to Remove
})
Let's start with the end. You have a refresh token, you won't to destroy it. Simply filter the array against this token and submit a status. The token becomes unusable after it's cleared from the array, that's the goal here.
route.delete("/logout", async (req, res) => {
refreshTokens = refreshTokens.filter((token) => token != req.body.token)
res.sendStatus(204)
})
With me so far? Now let's jump back to the start. If you log in with an email and password, if they're wrong respond with an error message, if they're correct receive the tokens.
route.post("/login", async (req, res) => {
const username = req.body.username
const password = req.body.password
// This is just a quick demonstration,
// you would have to use the bcrypt hash
// or other hash/salt methods. DO NOT
// STORE passwords plaintext
// Not existent user = Unauthorized
if (username != 'admin') return res.sendStatus(401)
// Wrong Password = Forbidden
if (password != 'abc123') return res.sendStatus(403)
const user = {
id: 0,
username: username,
password: password
}
const accessToken = generateAccessToken(user)
const refreshToken = generateRefreshToken(user)
let result = {
success: true,
accessToken: accessToken,
refreshToken: refreshToken,
}
res.send(result)
})
Now how do we sign the JSON web tokens? Let's take a look at the two methods used here:
function generateAccessToken(content) {
return jwt.sign(content, process.env.ACCESS_TOKEN_SECRET, {
expiresIn: "15m",
})
}
function generateRefreshToken(content) {
const token = jwt.sign(content, process.env.REFRESH_TOKEN_SECRET)
refreshTokens.push(token)
return token
}
Both use some sort of environment tokens, but why? That's the token you will have to generate once for the back end. It will be used as a public key. We simply generate the access tokens for 15 minutes and push the refresh tokens to the array.
route.post("/token", async (req, res) => {
const refreshToken = req.body.token
if (refreshToken == null) return res.sendStatus(401)
if (!refreshTokens.includes(refreshToken)) return res.sendStatus(403)
jwt.verify(refreshToken, process.env.REFRESH_TOKEN_SECRET, (err, user) => {
if (err) return res.sendStatus(403)
res.json({ accessToken:
generateAccessToken({
id: 0,
username: user.name,
password: user.password
})
})
})
})
We verify the refresh token, if it exists and it is valid, return a new access token for 15 minutes. That's it for the token part, you can login (create refresh token), retrieve an access token and logout (kill refresh token)
How to Use: Authenticate and Authorize
Admin pages should return 403 while the forum board should be different whether you're logging as a guest or an actual user. The first one is authentication, the second authorization.
Let's create two functions for each. Express is quite handy with the next() function
exports.authenticate = function (req, res, next) {
const authHeader = req.headers["authorization"]
const token = authHeader?.split(" ")[1]
jwt.verify(token || "", process.env.ACCESS_TOKEN_SECRET, (err, user) => {
req.user = err ? {} : user
next()
});
};
exports.authorize = function (req, res, next) {
const authHeader = req.headers["authorization"]
const token = authHeader?.split(" ")[1]
if (token == null)
return res.sendStatus(401)
jwt.verify(token, process.env.ACCESS_TOKEN_SECRET, (err, user) => {
if (err) return res.sendStatus(403)
req.user = user
next()
})
}
Now you're done with the whole authentication system (aside some cleanup's) and probably the registration system. Let's make use of it.
Client side you can create a REST api like so:
POST http://localhost:8081/auth/login
Content-Type: application/json
{
"username": "admin",
"password": "abc123"
}
# Returns refresh and access token.
###
DELETE http://localhost:8081/auth/logout
Content-Type: application/json
{
"token": "REFRESH_TOKEN"
}
# Logs out a user.
###
POST http://localhost:8081/auth/token
Content-Type: application/json
{
"token": "REFRESH_TOKEN"
}
#
# This is how you can provide the access token
# when making a request to say a forum api
#
GET http://localhost:8081/forum/api/board/0
Authorization: Bearer ACCESS_TOKEN
Usage:
route.get("forum/board/:id", authenticate, async (req, res) => {
res.send(req.user)
})
Expected Output when going to localhost:8081/forum/board/7 authenticated:
{id:0,username:"admin",password:"abc123"}
Otherwise:
{}
Nevertheless, do not try implementing your own authentication. Really, you shouldn't.
Source
https://www.youtube.com/watch?v=mbsmsi7l3r4

401 Error when sending axios get request with Auth Headers

In my react project, I have been using axios to help get data from MongoDB.
My axios get requests return a 401 error when I add my auth middleware as a param. This middleware requires the user to have a valid JWT token before any data is returned.
When making the request with Insomnia/Postman, as long as long as the user token is added to the request headers, I always get the desired data returned. However, when I try to get the data within the react application, I get a "Failed to load resource: the server responded with a status of 401 (Unauthorized)" error log, along with my custom json error response, "No Auth token, authorisation denied!"
I built a simple boolean function that returns true if the current user has a valid JWT token. Despite returning true, I am still receiving the 401 error as a response, which leads me to suspect there is a syntax or formatting error somewhere inside my react code.
Backend (with express):
router.get("/allauth", auth, async (req, res) => {
const players = await Player.find();
res.json(players);
});
Auth:
const jwt = require("jsonwebtoken");
const auth = (req, res, next) => {
try {
const token = req.header("x-auth-token");
if (!token)
return res.status(401)
.json({ msg: "No Auth token, authorisation denied!" });
//match token against .env password
const verified = jwt.verify(token, process.env.JWT_SECRET);
if (!verified)
return res.status(401)
.json({ msg: "Token verification failed, authorisation denied!" });
req.user = verified.id;
next();
} catch (err) {
res.status(500).json({ error: err.message });
}
};
React frontend:
export default function DisplayTeam() {
const [players, setPlayers] = useState([]);
const {userData} = useContext(UserContext);
useEffect(() => {
let token = localStorage.getItem("auth-token");
const url = "http://localhost:5000/players";
console.log(token);
axios.get(`${url}/allauth`, {
headers: {
"Authorization": `x-auth-token ${token}`
}
})
.then( response => setPlayers(response.data))
.catch( error => console.log(error));
}, []);
return (//display from DB);
Based on your server config, you need to pass X-Auth-Token header not Authorization,
it should be:
headers: {
"X-Auth-Token": token
}
Also, Take into consideration that headers are Case Sensitive!

Authorization header value returns undefined in SuperTest

So I am writing tests using Mocha, Supertest and Chai. All my API's require an Authorization header which can be gotten from req.headers["authorization"]. This is the current setup of what my test looks like.
...
describe("Upload media files", () => {
it("uploads a file successfully", async () => {
const imagePath = path.join(__dirname, "../../fixtures/profile.jpeg");
const res = await app.post("/api/v1/account/upload") //app is global
.set("Authorization", "ey83hfjksksls...") //right here
.set("content-type", "multipart/form-data")
.attach("media", imagePath);
expect(res.status).to.eql(200);
expect(res.body.data).not.to.be.null;
expect(res.body.data).to.match(/https:/);
});
});
My tests always fail because a 401 is always returned for some reason that I do not know. Here's the function that handle's the decoding of the JWT
async function decodeToken() {
const sessionUser = req.session.userId;
const token = req.headers["authorization"] // token value is present here.
if (!token && !sessionUser) {
return res.status(401).json({
error: "Access denied, no valid credentials.",
});
}
if (token) {
const secret = sails.config.custom.jwtSecret || process.env.JWT_SECRET;
const decoded = jwt.verify(token, secret);
req.me = decoded;
return proceed();
}
const user = await User.findOne({
id: sessionUser,
});
req.me = user;
return proceed();
}
From the function above, when I do a console.log of the token variable, I do get a long string returned which shows that the token is being generated and passed into the .set('Authorization', 'eyeur9339..') header, but the test still fails. I don't know what to do anymore. Any help is appreciated, thank you.
if you use jwt for authorization, use Bearer before token so you should write Bearer, like this :
Bearer 5d5bd7f7e35a86541686f96c9cdd72ed67f5ba223811634e0319af224164823edc2d0090d1f6c2c3b1dec842ea783c71feb443ac2d26e
and for get token from header do like this :
const token = req.headers.authorization.split(' ')[1]
const decodedToken = jwt.verify(token, secret)

Categories

Resources