How to save JWT Token in Vuex with Nuxt Auth Module? - express

I am currently trying to convert a VueJS page to NuxtJS with VueJS. Unfortunately I have some problems with authenticating the user and I can't find a solution in Google. I only use Nuxt for the client. The API is completely separate in express and works with the existing VueJS site.
In Nuxt I send now with the Auth module a request with username and password to my express Server/Api. The Api receives the data, checks it, and finds the account in MongoDB. This works exactly as it should. Or as I think it should. Now I take the user object and generate the jwt from it. I can debug everything up to here and it works.
Now I probably just don't know how to keep debugging it. I send an answer with res.json(user, token) back to the Nuxt client (code follows below). As I said, in my current VueJS page I can handle this as well. Also in the Nuxt page I see the answer in the dev console and to my knowledge the answer fits.
Now some code.
The login part on the express Api:
const User = require('../models/User')
const jwt = require('jsonwebtoken')
const config = require('../config/config')
function jwtSignUser(user){
const ONE_YEAR = 60 * 60 * 24 * 365
return jwt.sign(user,config.authentication.jwtSecret, {
expiresIn: ONE_YEAR
})
}
module.exports = {
async login (req, res){
console.log(req.body)
try{
const {username, password} = req.body
const user = await User.findOne({
username: username
})
if(!user){
return res.status(403).send({
error: `The login information was incorrect.`
})
}
const isPasswordValid = await user.comparePassword(password)
if(!isPasswordValid) {
return res.status(403).send({
error: `The login information was incorrect.`
})
}
const userJson = user.toJSON()
res.json({
user: userJson,
token: jwtSignUser(userJson)
})
} catch (err) {
console.log(err)
res.status(500).send({
error: `An error has occured trying to log in.`
})
}
}
}
nuxt.config.js:
auth: {
strategies: {
local: {
endpoints: {
login: {url: '/login', method: 'post' },
user: {url: '/user', method: 'get' },
logout: false,
}
}
},
redirect: {
login: '/profile',
logout: '/',
user: '/profile',
callback:'/'
}
}
even tried it with nearly any possible "propertyName".
and, last but not least, the method on my login.vue:
async login() {
try {
console.log('Logging in...')
await this.$auth.loginWith('local', {
data: {
"username": this.username,
"password": this.password
}
}).catch(e => {
console.log('Failed Logging In');
})
if (this.$auth.loggedIn) {
console.log('Successfully Logged In');
}
}catch (e) {
console.log('Username or Password wrong');
console.log('Error: ', e);
}
}
What I really don't understand here... I always get "Loggin in..." displayed in the console. None of the error messages.
I get 4 new entries in the "Network" Tag in Chrome Dev Tools every time I make a request (press the Login Button). Two times "login" and directly afterwards two times "user".
The first "login" entry is as follow (in the General Headers):
Request URL: http://localhost:3001/login
Request Method: OPTIONS
Status Code: 204 No Content
Remote Address: [::1]:3001
Referrer Policy: no-referrer-when-downgrade
The first "user" entry:
Request URL: http://localhost:3001/user
Request Method: OPTIONS
Status Code: 204 No Content
Remote Address: [::1]:3001
Referrer Policy: no-referrer-when-downgrade
Both without any Response.
The second login entry:
Request URL: http://localhost:3001/login
Request Method: POST
Status Code: 200 OK
Remote Address: [::1]:3001
Referrer Policy: no-referrer-when-downgrade
and the Response is the object with the token and the user object.
The second user entry:
Request URL: http://localhost:3001/user
Request Method: GET
Status Code: 200 OK
Remote Address: [::1]:3001
Referrer Policy: no-referrer-when-downgrade
and the Response is the user object.
I think for the login should only the login request be relevant, or I'm wrong? And the user request works because the client has asked for the user route and the user route, always send the answer with the actual user object in my Express API.
Because I think, the problem is in the login response? Here some screenshots from the Network Tab in Chrome Dev Tools with the Request/Response for login.
First login request without response
Second login request
Response to second login request
Do I have to do something with my Vuex Store? I never found any configured Vuex Stores in examples for using the Auth Module while using google so I thougt I do not have to change here anything.
Thats my Vuex Store (Vue Dev Tools in Chrome) after trying to login without success:
{"navbar":false,"token":null,"user":null,"isUserLoggedIn":false,"access":false,"auth":{"user":"__vue_devtool_undefined__","loggedIn":false,"strategy":"local","busy":false},"feedType":"popular"}
There is also some logic I use for my actual VueJS site. I will remove that when the Auth Module is working.
Asked by #imreBoersma :
My /user endpoint on Express looks like:
app.get('/user',
isAuthenticated,
UsersController.getUser)
I first check if the User is authenticated:
const passport = require('passport')
module.exports = function (req, res, next) {
passport.authenticate('jwt', function (err, user) {
if(err || !user) {
res.status(403).send({
error: 'You are not authorized to do this.'
})
} else {
req.user = user
next()
}
})(req, res, next)
}
After that I search the User document in MongoDB and send the document to the client:
const User = require('../models/User')
module.exports = {
[...]
getUser (req, res) {
User.findById(req.user._id, function (error, user){
if (error) { console.error(error); }
res.send(user)
})
}
[...]
}
Feel free to ask for more information.

I think I can answer my own question.
I searched the whole time for an error regarding to my api response.
The problem was the "propertyName" on user endpoint in the nuxt.config.js.
It is set to "user" as default. When I set it to "propertyName: false", than everything works as it should.
auth: {
strategies: {
local: {
endpoints: {
login: {url: '/login', method: 'post', propertyName: 'token' },
user: {url: '/user', method: 'get', propertyName: false },
logout: false,
}
}
}
},

Related

Cookie is sent, but not stored when deployed

I'm having issues with cookies in a MERN app hosted to Vercel (Front-end) and Heroku (Back-end).
Everything is working fine in localhost, but when deployed i'm having issues storing cookies. Set-Cookie is sent with the sign in request, and the request itself looks fine. I get no errors.
After being signed in, authenticated routes return what I expect for each user, so the cookie seems to be accessible to the back-end even tho the cookie is not stored on the front-end under storage, which I assume is a security issue. This is not the case in Safari, in Safari the cookies are gone after login, so the user is logged out again.
The only route that does not work is signing out. On sign out I clear the token cookie, but it is trying to delete something that technically isn't there, still no errors. UPDATE: Clearing cookie works! I wasn't sending any data back with the sign out, so the front-end thought it was unresponsive, adding a .send({ message: 'Sign Out Successful' }) solved that issue. However, still no cookie visible in the front-end storage.
For CORS settings I have the origin set to the front-end url, and credentials set to true. While on the front-end I have withCredentials: true set on every request.
When deployed, the cookie is using sameSite: none, and secure: true.
Below are the sign in and sign out routes, but you can find the full back-end code here, and the front-end code here on GitHub.
Sign in route on back-end
import type Route from '../../types/Route'
import jwt from 'jsonwebtoken'
import env from '../../env/env'
import bcrypt from 'bcryptjs'
import User from '../../models/User'
import validateSignIn from '../../utils/validation/signIn'
const route: Route = {
method: 'post',
execute: async (req, res) => {
const { username, password } = req.body.user
try {
const { errors, valid } = validateSignIn(username, password)
if (!valid) return res.status(401).send({ errors })
const user = await User.findOne({ username })
if (!user) return res.status(404).send({ errors: { username: 'User not found' }})
const correctPassword = await bcrypt.compare(password, user.password)
if (!correctPassword) return res.status(401).send({ errors: { password: 'Wrong password' } })
const token = jwt.sign({ userId: user._id }, env.SECRET, { expiresIn: "1hr" })
return res.status(200).cookie('token', token, {
expires: new Date(Date.now() + 604800000),
secure: env.ENVIRONMENT === 'LIVE',
sameSite: env.ENVIRONMENT === 'LIVE' ? 'none' : 'lax',
httpOnly: true
}).send(user)
} catch (error) {
console.log('#sign/in', error)
return res.sendStatus(500)
}
}
}
export default route
Sign out route on back-end:
import type Route from '../../types/Route'
import authorization from '../../middlewares/http'
import env from '../../env/env'
// TODO: Fix Sign Out
const route: Route = {
method: 'get',
authorization,
execute: async (req, res) => {
try {
return res.clearCookie('token').sendStatus(200)
} catch (error) {
console.log('#sign/out', error)
return res.status(500)
}
}
}
export default route
The .clearCookie() doc says
Web browsers and other compliant clients will only clear the cookie if the given options is identical to those given to res.cookie(), excluding expires and maxAge.
So, try making your clear operation match your set operation, something like this.
res
.clearCookie('token', token, {
secure: env.ENVIRONMENT === 'LIVE',
sameSite: env.ENVIRONMENT === 'LIVE' ? 'none' : 'lax',
httpOnly: true })
.sendStatus(200)

How to prevent nuxt auth to go to error page if the pass or email is wrong

I'm using NuxtJs v2.13 with its auth module and Laravel with passport for my backend. for login i use the documented method:
async signIn(formData){
await this.$auth.loginWith('local',{
data: formData
})
if(this.$auth.user.depth > 1){
this.goTo('/cms/product')
}else{
this.goTo('/')
}
}
when the email or password is wrong it send me too nuxt error page! i should remain on login page.
what should i do!!?
BTW, i gonna use vee-validate on my form too. and this is my auth config on nuxt.config.js:
auth: {
strategies: {
local: {
endpoints: {
login: { url: 'auth/login', method: 'post', propertyName: '' },
logout: { url: 'auth/logout', method: 'post' },
user: { url: 'auth/info', method: 'get', propertyName: 'data' }
}
}
},
redirect: {
login: '/login',
logout: '/',
callback: '/login',
home: '/'
},
cookie: {
prefix: 'auth.',
options: {
path: '/',
maxAge: process.env.AUTH_COOKIE_MAX_AGE
}
}
},
Nuxt is redirecting because the error isn't being handled. You can simply wrap this code in an error handler. It's also good to put this code near the login component or page so you can use the status code of the error to display some meaningful response to the user, e.g. that the credentials were invalid.
try {
await this.$auth.loginWith('local', {
data: formData,
})
if (this.$auth.user.depth > 1) {
this.goTo('/cms/product')
} else {
this.goTo('/')
}
} catch (error) {
if (error.response) {
// Get the error status, inform the user of the error.
}
// Unexpected error, tell the user to try again later.
}
Since the #nuxtjs/auth package requires the #nuxtjs/axios package you can also read about intercepting errors on a global level with Axios Interceptors. I personally use try/catch blocks at the method level and use interceptors for catching 401 Unauthenticated errors and deleting the user information from Vuex.

How to properly use passport-github for REST API authentication?

I am building a vue.js client which needs to be authenticated through github oauth using an express server. It's easy to do this using server side rendering but REST API has been troublesome for me.
I have set the homepage url as "http://localhost:3000" where the server runs and I want the authorization callback url to be "http://localhost:8080" (which hosts the client). I am redirecting to "http://localhost:3000/auth/github/redirect" instead, and in its callback redirecting to "http://localhost:8080". The problem I am facing is that I am unable to send user data to the vuejs client through res.redirect. I am not sure if I am doing it the right way.
router.get("/github", passport.authenticate("github"));
router.get(
"/github/redirect",
passport.authenticate("github", { failureRedirect: "/login" }),
(req, res) => {
// res.send(req.user);
res.redirect("http://localhost:8080/"); // req.user should be sent with this
}
);
I have implemented the following approach as a work around :-
A route that returns the user details in a get request :
router.get("/check", (req, res) => {
if (req.user === undefined) {
res.json({});
} else {
res.json({
user: req.user
});
}
});
The client app hits this api right after redirection along with some necessary headers :
checkIfLoggedIn() {
const url = `${API_ROOT}auth/check/`;
return axios(url, {
headers: { "Content-Type": "application/json" },
withCredentials: true
});
}
To enable credentials, we have to pass the following options while configuring cors :
var corsOption = {
origin: true,
credentials: true
};
app.use(cors(corsOption));

#nuxtjs/auth login method calls a user method upon completion

I'm using the nuxtjs/auth module to authenticate a user, with the backend written in .NET Core
When configuring the nuxtjs/auth package, I add the following to nuxt.config.js
//nuxt.config.js
.
.
auth: {
strategies: {
local: {
endpoints: {
login: { url: 'users/authenticate', method: 'post', propertyName: 'data.token' },
}
}
}
},
.
.
the login function called when clicking 'login' looks like this
async login () {
try {
await this.$auth.loginWith('local', {
data: {
Username: this.username,
Password: this.password
}
})
this.$router.push('/')
} catch (e) {
console.log(e) <-------------------
this.error = e.response.data.message
}
}
The console.log(e) returns the following message:
Error: Request failed with status code 404
at createError (commons.app.js:565)
at settle (commons.app.js:728)
at XMLHttpRequest.handleLoad (commons.app.js:98)
And in the console I also get
GET https://example.com/api/api/auth/user 404 (Not Found)
So it would seem that upon completing the initial authentication request, the nuxtjs/auth module calls 'user' in the API.
My backend is written in NET Core
So my question is, am I supposed to write in the NET Core backend, because the documentation for the nuxtjs/api package does not seem to explain how the basic usage of the module works

graphql server email verify example

I'm starting to work on an express API using graphql with apollo-server-express and graphql-tools. My register user process steps are:
User submit user name, email and password.
Server send an email to user by Mailgun with unique link generated by uuid.
User follow the link to verify the registration.
But I'm in struggle at how to bind the mutation in the resolver. See snippets:
server.js
const buildOptions = async (req, res, done) => {
const user = await authenticate(req, mongo.Users)
return {
schema,
context: {
dataloaders: buildDataloaders(mongo),
mongo,
user
},
}
done()
}
// JWT setting
app.use('/graphAPI',
jwt({
secret: JWT_SECRET,
credentialsRequired: false,
}),
graphqlExpress(buildOptions),
res => data => res.send(JSON.stringify(data))
)
Mutation on resolver
signupUser: async (root, data, {mongo: { Users }}) => {
// Check existed accounts,
// if account is not exist, assign new account
const existed = await Users.findOne({email: data.email})
if (!existed) {
// create a token for sending email
const registrationToken = {
token: uuid.v4(),
created_at: new Date(),
expireAfterSeconds: 3600000 * 6 // half day
}
const newUser = {
name: data.name,
email: data.email,
password: await bcrypt.hash(data.password, 10),
created_at: new Date(),
verification_token: registrationToken,
is_verified: false,
}
const response = await Users.insert(newUser)
// send and email to user
await verifyEmail(newUser)
return Object.assign({id: response.insertedIds[0]}, newUser)
}
// Throw error when account existed
const error = new Error('Email existed')
error.status = 409
throw error
},
// VERIFY USER
// Set verify to true (after user click on the link)
// Add user to mailist
verifiedUser: async (root, data, {mongo: { Users }}) => {
await Users.updateOne(
{ email: data.email },
{
set: {is_verified: true},
unset: {verification_token: {token: ''}}
}
)
},
route config
routes.get('/verify?:token', (req, res, next) => {
res.render('verified', {title: 'Success'})
})
the route config is where I stuck, because the object is passed to all resolvers via the context inside graphqlExpress
Any one help me out or suggest for me any articles related. Thanks so much.
You will need 3 graphql endpoints and 1 apollo http endpoint for proper workflow.
Optionally you can combine 3 graphql endpoints in one, but then it will be a one big function with a lot of different responsibilities.
1# graphql endpoint: changepass-request
expects email param
check if user with such email found in db:
generate code
save it in the local account node
send code to the user email with http link to confirm code:
http://yoursite.com/auth/verify?code=1234
return redirect_uri: http://yoursite.com/auth/confirm-code
for UI page with prompt for confirmation code
2# graphql endpoint: changepass-confirm
expects code param:
if user with such code found in db, return redirect_uri to UI page with prompt for new pass with confirmation code in params: http://yoursite.com/auth/change-pass?code=1234
3# graphql endpoint: changepass-complete
expects code and new pass:
hash new password
search in db for local account with such code
3a. if not found:
return error with redirect_uri to login page:
http://yoursite.com/auth?success=false&message="Confirmation code is not correct, try again."
3b. if found:
change password for new, return success status with redirect_uri to login page:
http://yoursite.com/auth?success=true&message="ok"
4# apollo HTTP endpoint: http://yoursite.com/auth/verify?code=1234
if no code provided:
redirect to UI registration page with error message in params:
http://yoursite.com/auth?success=false&message="Confirmation code is not correct, try again."
if code provided: search in db for local account with such code
1a. if user not found:
redirect to reg ui with err mess in params:
http://yoursite.com/auth?success=false&message="Confirmation code is not correct, try again."
1.b if user found:
redirect to ui page with new password prompt and attach new code to params
I didn't put any code above, so you can use this workflow in other auth scenarios.
It seems like rather than utilizing the verifiedUser endpoint, it would be simpler to just keep that logic inside the controller for the /verify route. Something like:
routes.get('/verify?:token', (req, res) => {
Users.updateOne(
{ verification_token: { token } },
{
$set: {is_verified: true},
$unset: {verification_token: {token: ''}}
},
(err, data) => {
const status = err ? 'Failure' : 'Success'
res.render('verified', {title: status})
}
)
})