I'm trying to pass some data from my local backend using nest.JS, the login is successful and the jwt token is shown in the cookies, but the error says:
[Nest] 39 - 02/22/2022, 9:50:59 AM ERROR [ExceptionsHandler] jwt must be provided
nest-admin-backend-1 | JsonWebTokenError: jwt must be provided
nest-admin-backend-1 | at Object.module.exports [as verify] (/app/node_modules/jsonwebtoken/verify.js:53:17)
nest-admin-backend-1 | at /app/node_modules/#nestjs/jwt/dist/jwt.service.js:42:53
nest-admin-backend-1 | at new Promise (<anonymous>)
nest-admin-backend-1 | at JwtService.verifyAsync (/app/node_modules/#nestjs/jwt/dist/jwt.service.js:42:16)
nest-admin-backend-1 | at AuthService.userId (/app/src/auth/auth.service.ts:16:44)
nest-admin-backend-1 | at AuthController.user (/app/src/auth/auth.controller.ts:68:43)
nest-admin-backend-1 | at /app/node_modules/#nestjs/core/router/router-execution-context.js:38:29
nest-admin-backend-1 | at processTicksAndRejections (node:internal/process/task_queues:93:5
as for the code, there is no error anywhere, I'm following the tutorial as it should and it doesn't work.
If I'm following the error message, it says the error on my auth.service & auth.controller file, so here is my file snippets:
Auth.controller
export class AuthController {
constructor(
private userService: UserService,
private jwtService: JwtService,
private authService: AuthService,
) {
}
#Post('register')
async register(#Body() body: RegisterDto) {
if (body.password !== body.password_confirm) {
throw new BadRequestException('Password do not match!');
}
const hashed = await bcrypt.hash(body.password, 12);
{ }
return this.userService.create({
firstName: body.firstName,
lastName: body.lastName,
email: body.email,
password: hashed,
role: { id: 1 }
});
}
#Post('login')
async login(
#Body('email') email: string,
#Body('password') password: string,
#Res({ passthrough: true }) response: Response,
) {
const user = await this.userService.findOne({ email });
if (!user) {
throw new NotFoundException('User not found!');
}
if (!await bcrypt.compare(password, (await user).password)) {
throw new BadRequestException('Invalid password!');
}
// Generate JWT
const jwt = await this.jwtService.signAsync({ id: user.id })
response.cookie('jwt', jwt, { httpOnly: true });
return user;
}
#UseGuards(AuthGuard) // This is a custom guard
// Authenticate user and generate JWT
#Get('user')
async user(#Req() request: Request) {
const id = await this.authService.userId(request);
// Get user from the database
return this.userService.findOne({ id });
}
#UseGuards(AuthGuard) // Check if user is authenticated
#Post('logout')
async logout(#Res({ passthrough: true }) response: Response) {
response.clearCookie('jwt');
return {
message: 'Logged out successfully',
}
}
}
Auth.service
export class AuthService {
constructor(private jwtService: JwtService) {
}
async userId(request: Request): Promise<number> {
const cookie = request['jwt'];
// Get data from the Cookie
const data = await this.jwtService.verifyAsync(cookie);
// Get user from the database
return data['id'];
}
}
I can't access the localhost:8000/api/user from the postman too, even if I'm already logged in. Any idea how to solve it?
Cookies are a protocol between HTTP servers and browsers so Postman and Backends can't just log in and have the cookie header sent.
To allow Applications (mobile, desktop, and server) to be identified by your API server will need to introduce an additional way to send the JWT.
Allow JWT to be sent as an HTTP header in addition to Cookies. Use the Authorization header as a secondary method to send the JWT in.
To achieve this you will need to:
modify your /login to return the JWT as plain text (instead of User)
#Post('login')
async login(
#Body('email') email: string,
#Body('password') password: string,
#Res({ passthrough: true }) response: Response,
) {
const user = await this.userService.findOne({ email });
...
// Generate JWT
const jwt = await this.jwtService.signAsync({ id: user.id })
response.cookie('jwt', jwt, { httpOnly: true }); // <-- for browsers
return jwt; // <--- for applications
}
Now it's the application's responsibility to store and send JWT (using the Authorization header) on subsequences request
Update your AuthGuard to check for JWT in the cookie and Authorization header.
Related
I hope someone can help me! I am trying to create a register/login form, and doing so, I was thinking to save the JWT token in cookie session. Even if in Postman everything is ok, every time that I try to get back cookies in orderd to take the take and verify if it exists to protect the route, I always get "undefined"! I'm going crazy.
FILE SERVER.TS: Here i call the method use() in order to mount the middlewars
//ALL IMPORT HERE
const port = env.PORT
const path = "/users"
const url = env.URL
const server = express()
server.use(express.json())
server.use(cookieParser())
server.use(policyCors())
server.use(path, router)
THIS IS THE LOGIN MIDDLEWARE:
router.post(login, async(request: Request, response: Response, next: NextFunction) => {
const { email, password, } = request.body
const user = await prisma.users.findFirst({
where: { email: email }
})
if (user && await bcrypt.compare(password, user.password)) {
const payload = { email }
const token = jwt.sign({ email: user.email }, "String(secret)", { expiresIn: "30m" })
response.cookie("token", token, {
maxAge: 60*60*24*30*1000
})
response.json({ token: token })
}
})
This is the token verifier function:
const authToken = (request: Request, response: Response, next: NextFunction) => {
const accessToken = request.cookies["token"]
console.log(accessToken)
next()
}
router.get("/account", authToken, (request: Request, response: Response, next: NextFunction) => {
response.json("ok")
})
I expect to receive back the token from the cookie
i am at initial stage of learning mern stack i dont know to how store the login history whenever a user login
i tried to access with jwt token which is stored in my localstorage
app.post("/apexa_Admin_login", async (req, res) => { //creating login api
let isAuthorized = await registermodule.findOne({ userid: req.body.userid, passwordHash: req.body.passwordHash })//using isAuthorized variable along with findone method
try {
if (isAuthorized) {
const token = await isAuthorized.generateToken();
res.status(200).send({
message: "we valid",
token: token,
id: isAuthorized._id,
userid: isAuthorized.userid,
passwordHash: isAuthorized.passwordHash,
// status: 200
})
//try to access with token
let getTokenHistory = localStorage.getItem("token")
console.log("token history", getTokenHistory)
//passing a message
console.log(token, "token")
}
else {
res.status(404).send('Sorry, cant find that');
}
} catch {
logger.error(new Error(`File Name: ${path.basename(__filename)} | Method Name : Login| Message: Catch Error`))
}
})
Hey so I am try to use express-session and connect-mongodb-session, TypeScript Express/Node Api now what I want to do is when a user is logged I will be using express-session to make that cookie and automatically that cookie is persisted to MongoDB. Now the problem that I am having is I want to add detail to that session i.e the user's info so that when persisting the user I know their username etc...
Now when not using TypeScript I can simply do something like this to add user detail to session being instatiated:
request.session.user = {username:'John', id='97y9797977c9q7dw7y9qw7d9721'}
But now when using TypeScript I get hit by the following error when I try to do the same as above:\
Error: Property 'user' does not exist on type 'Session & Partial'
Code Below: This is my setup for express-session and connect-mongodb-session
const store = MongoStore(expressSession);
const mongoURI = process.env.mongoURI;
const mongoStore = new store({
collection: 'usersessions',
uri: mongoURI,
expires: 10 * 60 * 60 * 24 * 1000
});
app.use(
expressSession({
name: '_sid',
secret: process.env.session_secret,
resave: false,
saveUninitialized: false,
store: mongoStore,
cookie: {
httpOnly: true,
maxAge: 10 * 60 * 60 * 24 * 1000,
secure: process.env.NODE_ENV === 'production'
}
})
);
Code below is my SignIn method controller
SignIn(request: Request, response: Response) {
const form = new Formidable.IncomingForm();
try {
form.parse(request, async (error, fields, files) => {
if (error) {
return response.status(500).json({
msg: 'Network Error: Please try again later'
});
}
const { username, password } = fields;
if (!username || !password) {
return response.status(400).json({ msg: 'All fields are required' });
}
const user: any = await userModel.findOne({
usernam: username
});
if (!user) {
return response.status(404).json({
msg: 'Account with this username does not exist'
});
}
const hashedPassword = user.password;
const isPasswordValid = await Bcrypt.compare(password, hashedPassword);
if (!isPasswordValid) {
return response.status(400).json({ msg: 'Invalid credentials' });
}
const isUserSessionExisting = await userSession.findOne({
'session.user.username': username
});
if (isUserSessionExisting) {
return response
.status(200)
.json({ msg: 'Account already logged in' });
}
const userSessionObj = {
username: user.username,
id: user._id
};
request.session.user = userSessionObj; //This is where the error is coming
return response.status(200).send(request.sessionID);
});
} catch (error) {
return response
.status(500)
.json({ msg: 'Network Error: Please try again later' });
}
}
How can I resolve this issue
you have 2 ways to declare session.
import {Request} from "express"
type Req= Request & { session: Express.Session }; // this will be merges
or
declare global {
namespace Express {
interface Request {
// currentUser might not be defined if it is not logged in
session: Express.Session;
}
}
}
and this is Session interface:
interface Session extends SessionData {
id: string;
regenerate(callback: (err: any) => void): void;
destroy(callback: (err: any) => void): void;
reload(callback: (err: any) => void): void;
save(callback: (err: any) => void): void;
touch(): void;
cookie: SessionCookie;
}
as you can see there is no user property. Because usually we store a unique identifier in the cookie which is database id. Because user name can be changed in the future but user's database id or if you are doing google oauth authentication, user's google id dont change. Storing id would be enough. But if you still wanna attach user object to the session, create a new User interface
interface User{
username: string;
id:string
}
type NewSession=Express.Session & User
declare global {
namespace Express {
interface Request {
// currentUser might not be defined if it is not logged in
session: NewSession;
}
}
}
A better approach can be
const { session }: { session: Session & Partial<YourCustomType> } = request;
I'm using nodejs/express and express-jwt module.
this is the server code involved:
async function authenticate({ username, password }) {
const user = await User.findOne({ username });
if (user && bcrypt.compareSync(password, user.hash)) {
if(user.accountActive === true) {
const {hash, ...userWithoutHash} = user.toObject();
const token = jwt.sign({sub: user.id}, config.secret, { expiresIn: '1h' });
console.log(token)
return {
...userWithoutHash, token
};
} else {
throw 'Your account is pending approval.'
}
}
}
function jwt() {
const secret = config.secret;
return expressJwt({ secret, isRevoked }).unless({
path: [
// public routes that don't require authentication
'/users/authenticate',
'/users/register',
]
});
}
i am having {message: "Invalid Token"} message: "Invalid Token" whenever i tried to make a call to any api within the app
Here is the request header: the token is not added to the request headers
i add the authorization header with jwt token if available on the localstorage
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const currentUser = JSON.parse(localStorage.getItem('currentUser'));
if (currentUser && currentUser.token) {
request = request.clone({
setHeaders: {
Authorization: `Bearer ${currentUser.token}`
}
});
}
return next.handle(request);
}
when i login the url logs in and sends the token to an authenticate post route
// routes
router.post('/authenticate', authenticate);
module.exports = router;
function authenticate(req, res, next) {
userService.authenticate(req.body)
.then(user => user ? res.json(user) : res.status(400).json({ message: 'username or password is incorrect' }))
.catch(err => next(err));
}
Here is an example of the authenticate login session request after signin
it seems like jwt token even though its valid it becomes invalid after signin.
to me it seems like a token issue, but i don't know where the problem at exactly?
N.B I finally found where the problem was , i did not add the jwt interceptor to my app.module
by adding the bellow code to the providers fixed it for me:
{ provide: HTTP_INTERCEPTORS, useClass: JwtInterceptor, multi: true },
This mean that for the token i am providing a value, more than one value (or class) is going to be used.
angular-cli: 8.3.0
node: 10.16
express: 4.17.1
express-jwt: 5.3.1
I have an Adonis.js api-only app and my auth routes are not working.
Here is my signup route:
const Route = use('Route')
...
Route.post('/signup', 'UserController.signup')
Here is the action in the UserController:
'use strict'
const User = use('App/Models/User')
const Hash = use('Hash')
const Writ = use('App/Models/Writ')
class UserController {
async signup ({ request, auth, response }) {
// get user data from signup form
const userData = request.only(['name', 'username', 'email', 'password'])
console.log(userData);
try {
// save user to database
const user = await User.create(userData)
console.log(user);
// generate JWT token for user
const token = await auth.generate(user)
return response.json({
status: 'success',
data: token
})
} catch (error) {
return response.status(400).json({
status: 'error',
message: 'There was a problem creating the user, please try again later.'
})
}
}
...
module.exports = UserController
Using Postman, the console prints the request but returns:
{
"status": "error",
"message": "There was a problem creating the user, please try again later."
}
I hope you put all configuration right as mention in this document.
if your config right then this issue is user migration issue.
because user migration don't content name field so first check without send name into postman and not get name in controller like this
'use strict'
const User = use('App/Models/User')
const Hash = use('Hash')
const Writ = use('App/Models/Writ')
class UserController {
async signup ({ request, auth, response }) {
const userData =request.only(['username','email','password'])
console.log(userData);
try {
const user = await User.create(userData)
console.log(user);
// generate JWT token for user
const token = await auth.generate(user)
return response.json({
status: 'success',
data: token
})
} catch (error) {
return response.status(400).json({
status: 'error',
message: error
})
}
}
...
module.exports = UserController
and then try to generate token it's work
if you get success in response then change migration of user
try it:
'use strict'
const User = use('App/Models/User')
const Hash = use('Hash')
class UserController {
async signup ({ request, auth, response }) {
// get user data from signup form
const userData = request.only(['username', 'email', 'password'])
// ! Only existing fields in the database
console.log(userData);
try {
// save user to databas
const user = new User()
user.fill(userData)
await user.save()
// generate JWT token for user
const token = await auth.generate(user)
return response.json({
status: 'success',
data: token
})
} catch (error) {
return response.status(400).json({
status: 'error',
message: error
})
}
}
}
module.exports = UserController
It would be interesting to add a try/catch when creating the user to better target errors.
If it doesn't work, check the configuration files.
Have a nice day!