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.
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 have a controller that receives an user that is trying to login via form. When all validations are checked, the user will be logged in and a token will be created in the following way:
const token = jwt.sign({userId: user._id}, config.secret ,{expiresIn: '24h'})
res.json({success: true, message: 'Sesión iniciada', token: token, user: {email: user.email}})
However, how do I access this token from another controller? I've seen that a good approach would be to create a middleware that intercepts such token, but I don't really know how to accomplish this.
I'd be happy only knowing how to get the token tho. I'm kinda new and I'm taking very small steps.
You should setup your client requests to send such token as #Vahid said.
Here's an example with axios
const instance = axios.create({
baseURL: 'https://some-domain.com/api',
// From the docs:
// `transformRequest` allows changes to the request data before it is sent to the server
// This is only applicable for request methods 'PUT', 'POST', 'PATCH' and 'DELETE'
// The last function in the array must return a string or an instance of Buffer, ArrayBuffer,
// FormData or Stream
// You may modify the headers object.
transformRequest: [function (data, headers) {
headers['Authorization'] = localStorage.getItem('jwt')
return data;
}],
})
export default instance
In case you also need GET request you can add:
export setAuthToken = (token) => {
instance.defaults.headers.common['Authorization'] = token;
}
Although you'll need to call it every time your JWT is renewed.
After that, you could catch it using the Middlewares to decode the token from the headers
app.use((req, res, next) => {
const authToken = req.headers['Authorization']
if(authToken) {
try {
const decoded = jwt.verify(authToken, config.secret)
req.user = decoded.userId
// Hopefully
// req.user = getUserById(decoded.userId)
next()
} catch(e) {
// Handle Errors or renewals
req.user = null
// You could either next() to continue or use 'res' to respond something
}
} else {
// Throw 403 if should be authorized
res.sendStatus(403)
}
})
This way you should be able to access req.user on any route defined after your middleware.
Eg:
app.post('/me', (req, res) => {
res.send(req.user)
})
Note that this is just one example of a global middleware. In other cases, you should be able to create custom middlewares based on which routes you want to protect or with which amount of permissions.
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.
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'm using JWT ("jsonwebtoken": "^5.4.0") with express 4 and jade.
I'm able to create the right Token, but How can i Pass this token in each call?
Where I have to store this token ? in headers or in localStorage?
For now I'm using CURL with Postman, and Set token in header in
x-access-token
Have I Do create a middleware that retrieve a token from Database and use this in each call?
thanks
You do not need to save and check the token from the database. This token such a mechanism can be decoded with only your-server, and if it was done that the token is valid. The code that you want to do should look like.
var cookieParser = require('cookie-parser')
app.use(cookieParser())
app.get('/login', function(req, res, next) {
var user = {name:'test'}; //!! find the user and check user from db then
var token = jwt.sign(user, 'secret', {
expiresInMinutes: 1440
});
res.cookie('auth',token);
res.send('ok');
});
app.use(function(req, res, next) {
var token = req.cookies.auth;
// decode token
if (token) {
jwt.verify(token, 'secret', function(err, token_data) {
if (err) {
return res.status(403).send('Error');
} else {
req.user_data = token_data;
next();
}
});
} else {
return res.status(403).send('No token');
}
});
Here you can find very nice article : https://scotch.io/tutorials/authenticate-a-node-js-api-with-json-web-tokens
I would recommend checking this out if you want local storage: https://www.npmjs.com/package/node-localstorage
But, with that said, you guys and girls wouldn't believe how long it took me to find res.cookie('auth' token) from the above answer. I scoured Google for hours, Passport docs, Express docs, GraphQL and authentication/authorization docs in an effort to find out how to get the token to the API in a stateless manner.
I already built JWT token security and secured my GraphQL resolvers with it, but then, I opted to use EJS along with graphql-request (approx same as Apollo Client), so I needed to find a way to pass the token to my middleware without using a server side session.
Storing a JWT token in cookies is fine especially if you take extra precautions such as signing the cookie, and I recall there are also options you can include that keep the cookie secure, so that other sites cannot see it if the "browser" allows access to cookies. If a cookie is signed with your server secret, the data inside the cookie simply cannot be altered and still be valid. The risk is always still someone leaking their token/cookie, and if that bothers you, do research into refresh tokens. However, API tokens are generally and should be kept tightly secret and safe. Your biggest annoyance will more likely be the requirement to maintain a blacklist of JWTs that expire a year from now if you set expiry to 1y.
I am just including my findings here because this question is actually a rare resource it seems...
Here is my Express middleware for authentication:
// AUTHENTICATION
app.use(async (req) => {
try {
const token = req.headers.authorization || req.cookies.auth
const { person } = await jwt.verify(token, SECRET)
req.person = person
return req.next()
} catch (e) {
return req.next()
}
})
You can see I am setting the token from the header with cookie as fallback. This supports my needs fine and allows me to use really any client with stateless security.
My logged in user is available as req.person in my views and GraphQL resolvers. If req.person is not set, the user is treated as not-logged-in.
I am using return req.next() which is important to note because calling next() without parameters is treated as "clean go-to next middleware and/or proceed to process request". If you include any string or object parameter, it will throw an error that can bubble down to your error handling middleware. You can try it yourself. Put return next('You are not authenticated.') in the catch block and you will see it halt the request before your route.
I use return next() because I handle authorization in the routes and in my resolvers. It allows more flexibility such as facilitating register and login mutations to be accessed by non-authenticated users.
Here is my GraphQL endpoint (I am using Apollo Server):
app.use('/graphql', bodyParser.json(), graphqlExpress((req) => {
const context = {
person: req.person
}
return {
schema,
context,
rootValue: null
}
}))
In my GraphQL resolvers, the third parameter of every query has context.person populated with req.person which comes from the above Authentication middleware.
That is really all a person needs to know.
Here is how I am using the NPM package called graphql-request:
https://www.npmjs.com/package/graphql-request
app.get('/allpeople', async (req, res) => {
try {
const client = new GraphQLClient(GRAPHQL_ENDPOINT, {
headers: { Authorization: req.headers.authorization || req.cookies.auth }
})
const query = `query allPeople($serialNumber: String!) {
allPeople(serialNumber: $serialNumber) {
id
created
status
email
}
}`
const variables = {
serialNumber: req.person
}
const response = await client.request(query, variables)
res.render('allpeople/list', { people: response.allPeople })
} catch (e) {
throw [`allPeople`, `${JSON.stringify(error, null, 2)}`]
}
})
I include this code because there are no "more advanced" example usages of graphql-request, and I like it so far. It is very concise and could easily be swapped out for Apollo Client if you venture into React.js. My examples here are also very relevant for anyone researching createNetworkInterface and new ApolloClient().