I have designed an authentication function that works perfectly when tested via my POSTMAN endpoint. This is what it looks like when the Authorization value is correct:
And this is what it looks like when I alter the Authorization value to deliberately fail the authorization process:
Find below the authorization code:
const jwt = require ('jsonwebtoken');
const authenticate = (req, res, next)=>{
try {
const token = req.headers.authorization.split(' ')[1]
const decode = jwt.verify(token, 'verySecretValue')
console.log('Authentication PASSED!');
next();
}
catch (error) {
res.json({
message: 'Authentication FAILED!'
})
}
}
module.exports = authenticate
And, now find below the code I use to render:
.
.
.
const authenticate = require('./authentication/authenticate.js');
.
.
.
.
app.get('/list', authenticate, async (req,res)=> {
let countyResult = await county();
let transId = await transactionId();
transModel.find({transIndustry: 'Pharmacy'}, (err, docs)=> {
if (!err)
{
res.render('list', {data : docs, countyName: countyResult, transId: transId});
}
else
{
// res.status(status).send(body);
}
})
});
However, when I try to access the endpoint/link/address above via the browser, I get this error message:
{"message":"Authentication FAILED!"}
I feel like this is an Authorization value issue, and I don't quite know how I should be passing this value when rendering the list page via res.render('list', {data : docs, countyName: countyResult, transId: transId});.
Looking forward to your help.
You may inspect the requests that your browser makes by opening the dev tools and going to the network tab. There, you can check if the correct header is passed.
Do you have any code in the frontend to pass the token in the header?
If that's not an option, you may explore setting the token in a cookie. This way, you are sure that every request to your backend will include it.
Related
I am working with Next-auth and rtk query. I need that when a request, any, returns a 401 unauthorized error, the page redirects directly to the login. How is it done?
I added 'maxAge: 60' to the [...nextauth].js file and also refetchInterval={30} refetchOnWindowFocus={true} to the component tried to find a similar solution, but it doesn't work
since you're using rtk query, you can update your apiSlice baseQuery function, to check for auth errors and redirect on that, my suggestion is this:
create a base query where you check for the 401 and any other error you want:
// try to execute the req, if it fails logout, and redirect to login.
const baseQueryWithAuth: BaseQueryFn = async (args, api, extraOptions) => {
const result = await baseQuery(args, api, extraOptions);
if (result.error?.status === 403 || result.error?.status === 401) {
// non authorized, then redirect to login page.
// if we have jwt, here u should update the access token
localStorage.removeItem(TOKEN_KEY_IN_LOCAL_STORAGE);
Router.replace('/auth/login');
}
return result;
};
in the snippet above, when I'm referring to token deletion as logout because the token is already invalid in the DB, so I just need to delete it in the front, so no invalidate request is needed.
the mentioned baseQuery can be done like this:
const baseUrl = `${process.env.NEXT_PUBLIC_API_PROTOCOL}://${process.env.NEXT_PUBLIC_API_HOST}/api`;
const TOKEN_KEY_IN_LOCAL_STORAGE = 'SavedToken';
const baseQuery = fetchBaseQuery({
baseUrl,
// credentials: 'include',
prepareHeaders: (headers) => {
// get the authentication token from local storage if it exists
const token = localStorage.getItem(TOKEN_KEY_IN_LOCAL_STORAGE);
if (token) {
headers.set('Authorization', token);
} else {
Router.replace('/auth/login');
}
return headers;
},
});
and then now since you have a working base query with auth support, you can use that to create a main rtk query apiSlice for your project:
// create api
export const apiSlice = createApi({
baseQuery: baseQueryWithAuth,
tagTypes: ['tag1', 'tag2', 'tag3'],
endpoints: (_builder) => ({}),
});
I'm fairly new to Express and NodeJS. I'm having trouble accessing my custom created header named auth-token when trying to verify the existing of said user first before allowing them to do any CRUD functionality in the system. It just returned 'undefined' instead of the token I placed in it.
So below is where I created my custom header named auth-token in my home GET router.
// Home GET Router
router.get('/', verifyUser, async (req, res) => {
// get user data by id
const dbData = await All.findById({ _id: req.user._id })
// store token passed as query 'tkn' in 'token' var
const token = req.query.tkn
// create custom header & render 'index.ejs' or homepage
res
.header('auth-token', token)
.render('index', { data: dbData.data })
}
I successfully able to create the custom header auth-token with no problem as shown below in my index or home page:
Right now, I'm trying to save new data inserted by user in the home page by using Home POST Router as shown below. But it will check first whether the user has the token or not using verifyUser1st function:
// Home POST Router
router.post('/', verifyUser1st, async (req, res) => {
// save new data code here...
}
And this is my verifyUser1st function:
function verifyUser1st(req, res, next) {
// get token from header
const token = req.header('auth-token') // this will return undefined
// if have, then allow/continue next(). If don't have, then return error message
if(!token) return res.status(401).json({ message: 'Accessed Denied!' }) // I got this error since token = undefined
try {
// verify the exist token
const varified = jwt.verify(token, process.env.TOKEN_4LOGINUSER)
req.user = varified
next()
} catch(err) {
res.status(400).json({ message: 'Invalid token!' })
}
}
But unfortunately it returns Accessed Denied since the token is undefined.
Should the auth-token be in Request Headers section (in blue circle image above) instead of Response Header section (in red circle image above) in order for it to work?
If yes, then how can I do that? If not, then can you help enlighten me of what things or topics should I learn first in order for me to make this work since I'm kinda new to this HTTP, Express and NodeJS environment?
to answer your question briefly - if you want to pass the auth token in the header then it should be passed in the request header.
However, if you want some middleware to check a token value that you can use later on in the processing chain, then just set it as a custom property on the req object and access it from there. There is no reason to try to jam something into the headers and then parse it out again later.
const auth = (req, res, next) => {
let { token } = req.body;
try {
console.log(token)
if (!token)
return res.status(401).json({ msg: 'no authentication token, authorisation denied' })
const verified = jwt.verify(token, process.env.JWT_KEY);
if (!verified)
return res.status(401).json({ msg: 'no authentication token, authorisation denied' })
req.user = verified.id;
next();
}
catch (err) {
res.status(500).json({ error: err.message });
}
}
So instead of sending the token in a Header we can either grab it from somewhere in the state, or in your case from the local storage.
In the front end we would do something like const token = localStorage.getItem("auth-token");
And then we would pass this token to the API request.
I am making a Multi-Purpose API Service and as I got the token in the URL working perfect and authorising as expected with a 200. I've been having issues with the token not authorising with curl command or superagent, as its always return a 401 error.
auth.js
const { DB } = require('../src/Routes/index.js');
module.exports = function auth(req, res, next) {
if(!req.query.apiKey) {
return res.status(401).json({"error": "401 Unauthorized", message: "API Token is missing in the query. You will need to generate a Token and put in the apiKey query."})
} else {
let check = DB.filter(k => k).map(i => i.apiToken);
console.log(check);
let t = check.some(e => e == req.query.apiKey)
if(t !== true)
return res.status(401).json({"error": "401 Unauthorized", message: "The API Key you provided is invalid. Please refer to our Docs () and Generate a Token ()"});
return next();
}
}
This is the middleware for the token, I am using this in my routers so then the token will authenticate. However, if I remove the if statement for checking if an API Token is present. It seem to fix the issue kinda but always authenticates with any key (even ones not saved in the db) and is still not properly fixed.
and an example for requesting endpoint with token on a Discord Command:
const { MessageEmbed } = require("discord.js");
const { get } = require("superagent");
exports.run = async (bot, message, args) => {
const { body } = await get(`https://example.com/generators/3000years`)
.query({ image: message.author.displayAvatarURL({ dynamic: true, size: 2048 }) })
.set("Authorization", `Bearer MY_TOKEN`);
const embed = new MessageEmbed()
.setTitle(`**3000 Years**`)
.attachFiles({ attachment: body, name: "3000years.png" })
.setImage("attachment://3000years.png")
.setColor(`#ed8a5c`);
message.channel.send(embed);
}
You can see that if I authorise with Superagent, it will not work and return a 401 Unauthorised in the console.
I would like to ask why this is doing this and if I did something wrong. Any help is appreciated.
I'm fairly new to JavaScript, Express & NodeJS. I'm having trouble where I'm trying to create a new header that contain a token when user login (as shown below in login POST router)
// Login POST Router
router.post('/login', async (req, res) => {
// 1. Validatation
const {error} = loginValidation(req.body)
// if has error don't save
if(error) return res.status(400).send(error.details[0].message)
// 2. Check the existance of user/email in db
const userEmail = await User.findOne({email: req.body.email})
// if 'same/exist', then throw 'message/error'
if(!userEmail) return res.status(400).send(`Email doesn't exist!`)
// 3. Chack password (is correct)
const validPassword = await bcrypt.compare(req.body.password, userEmail.password)
if(!validPassword) return res.status(400).send(`Invalid password!`)
// 4. Create & assign token to user
const token = jwt.sign({_id: userEmail._id}, process.env.TOKEN_LOGINUSER)
// add 'token' to header - just for identifier (can be any name)
res.header('auth-token', token).json({ message: 'Logged In'})
})
Where then the login page that make the fetch request (login.js coded in Vanilla JavaScript) will do a redirect to a home page (as shown below in login.js - Login page)
if(loginRespond === 'Logged In') {
let homeURL = '/index.html'
window.location.replace(homeURL)
}
then back in NodeJS, i have a router for home or '/' that use a function to verify the token first (as shown below)
router.get('/', verify, async (req, res) => {
const data = await All.findById({ _id: req.user._id})
res.json(data.data)
})
and have a JSON Web token verify function (as shown below)
// Middleware function
function verify(req, res, next) {
// get token from header
const token = req.header('auth-token')
// if have, then allow. If don't have, then don't allow
if(!token) return res.status(401).json({ message: 'Accessed denied!' })
try {
// verify the exist token
const varified = jwt.verify(token, process.env.TOKEN_4LOGINUSER)
req.user = varified
next()
} catch(err) {
res.status(400).json({ message: 'Invalid token!' })
}
}
And these are my Middlewares
// B. Global Middleware
// 1. Handle cors
app.use(cors())
// 2. Handle JSON body-parser
app.use(express.json())
Everything works fine when testing it with Postman. But unfortunately, not with the live site. Since auth-token was never created and can't be found in Response Header of the homepage in Chrome (as shown below)
Since I'm new to express & nodejs, I don't actually know how to tackle this prob. I've try searching it through google. But can't find the right sentence or keyword to search for.
Hope you guys can point me where to look at and what should I read or learn before attempting to do this.
You will need to expose your custom header by adding an Access-Control-Expose-Headers header.
res
.header('Access-Control-Expose-Headers', 'auth-token')
.header('auth-token', token)
.json({ message: 'Logged In'})
This is required for it to work in the browser but not for Postman.
The Access-Control-Expose-Headers response header indicates which headers can be exposed as part of the response by listing their names.
As an alternative, you can provide a custom options parameter to your cors middleware: app.use(cors({ exposedHeaders: 'auth-token' })). It expects a comma-delimited string (ex: ‘Content-Range,X-Content-Range’) or an array (ex: ['Content-Range', 'X-Content-Range']).
Follows documentation on this from MDN.
By default, only the 7 CORS-safelisted response headers are exposed:
Cache-Control
Content-Language
Content-Length
Content-Type
Expires
Last-Modified
Pragma
If you want clients to be able to access other headers, you have to list them using the Access-Control-Expose-Headers header.
before i started working with reactJS i was using express sessions (with expressJS of course) to determine whether user was authenticated or not, my middleware was passed in /profile URL like this router.use('/profile', middleware, require('./profilePageFile')) and if user was not authenticated i was redirecting to login page with simple code
if(!req.session.user){
res.redirect('/login')
}
i tried to use redirecting with react too but since react has it's own routing system (react-router-dom) and express is only needed for creating APIs when i was logging in /profile url it was still showing me page content and redirecting me after xxx milliseconds later, and i think it would be better practice if i have my profile page and main page on default url ( 'domain.com/' ), as i see many websites are using this technique including Facebook, at this point i was trying to make something like this: if user has not token or token expired, don't display some "hello user" button, otherwise display it. my only problem is that i do not know how to do that.
if i have boolean in my react state called isAuthenticated or something like this which determines whether user is authenticated or not according to the header that i send from server-side, it would be bad practice for security, i think, and also when i tried that, it did not work anyway. at this point only thing that i can do is to pass req.userId to client if token exists. this works but it is not enough, if anyone got the point i will be glad if i get help
here is my middleware code
const guard = (req, res, next) => {
const token =
req.body.token ||
req.query.token ||
req.headers["x-access-token"] ||
req.cookies.token;
if (!token) {
res.status(401).send({ auth: false });
} else {
jwt.verify(token, process.env.SECRET, function(err, decoded) {
if (err) {
return res.status(500).send({
message: err.message
});
}
req.userId = decoded.id;
res.status(200).send({ auth: true });
next();
});
}
};
I have made two changes to your code.
const guard = (req, res, next) => {
const token = req.body.token ||
req.query.token ||
req.headers['x-access-token'] ||
req.cookies.token;
if (!token) {
// Authentication failed: Token missing
return res.status(401).send({ auth: false })
}
jwt.verify(token, process.env.SECRET, function (err, decoded) {
if (err) {
// Authentication failed: Token invalid
return res.status(401).send({
auth: false,
message: err.message
})
}
req.userId = decoded.id
next()
})
}
First, inside the if(err) condition I have changed the status code to 401 because if the token is invalid, it will raise the error here.
Secondly, I have removed the res.status(200).send({auth:true}) from the bottom of the function.
This is because the middleware should pass on to the route (which we are trying to protect with the JWT check) to respond. This was responding to the request before it got to the actual route.