JWT authentication not work on Iphone devices - javascript

i am using jwt authentication in my demo app.https://musflix.netlify.app
my demo application github url:https://github.com/danimadmolil/musify
i use my forked version of json-serve-auth https://github.com/danimadmolil/json-server-auth
i make a change to original json-serve-auth to support jwt auth throught cookies.
login function is like so:
(req, res, next) => {
const { email, password } = req.body as User
const { db } = req.app
if (db == null) {
throw Error('You must bind the router db to the app')
}
const user = db.get('users').find({ email }).value() as User
if (!user) {
res.status(400).jsonp('Cannot find user')
return
}
bcrypt
.compare(password, user.password)
.then((same) => {
if (!same) throw 400
return new Promise<string>((resolve, reject) => {
jwt.sign(
{ email },
JWT_SECRET_KEY,
{ expiresIn: JWT_EXPIRES_IN, subject: String(user.id) },
(error, accessToken) => {
if (error) reject(error)
else resolve(accessToken!)
}
)
})
})
.then((accessToken: string) => {
const { password: _, ...userWithoutPassword } = user
res.cookie('Authorization', `Bearer ${accessToken}`, {
sameSite: 'none',
secure: true,
httpOnly: true,
domain: req.hostname,
path:"/",
expires:new Date(Date.now() + 24 * 60 * 60 * 1000)
})
res.status(200).jsonp({ accessToken, user: userWithoutPassword })
})
.catch((err) => {
if (err === 400) res.status(400).jsonp('Incorrect password')
else next(err)
})
}
the code for using cookie in sever.js file in main app is like so
server.get("/getAllPlaylists", (req, res) => {
let { db } = req.app;
try {
var { id: userId } = getUserByCookie(req);
console.log("useriD", userId);
if (userId) {
return res.send(getUserPlaylists(db, userId));
} else {
return res.send("you are not fucking authorized");
}
} catch (e) {
res.statusCode = 200;
return res.send("error");
}
});
function getUserByCookie(req) {
let { db } = req.app;
const [schema, token] = req.cookies.Authorization
? req.cookies.Authorization.split(" ")
: [undefined, undefined];
if (token && schema) {
const { email } = jwt.verify(token, constants.JWT_SECRET_KEY);
const user = db.get("users").find({ email }).value();
return user;
}
return undefined;
}
there is no problem with android devices and windows systems.
the problem is when i use iphone devices (like iphone 7 or newest) when use try to login it should set a Authorization cookie to browser and then on each request (i use credential:"include" with fetch api) browser should send Authorization cookie to serve. but only on iphone devices it not sending that
i know it is a long question,i am sorry
any one can help me please?

Related

Unable to verify emails with JWT, need to find user with email but can't send email in the email

I am trying to verify user emails with JWT. My current set up is that a JWT is sent to a user when they try to log in if they do not have a confirmed email.
When the email is sent it composes a URL with the token and then sends the request to the server to verify the email. It worked great in postman as I could easily add the email that I want to verify in the body. But I can't think of a way how to do it in the browser.
This is the code that should verify the email.
confirmEmail = async (req, res, next) => {
const { email } = req.body
const param = req.params.token
const user = await userModel.findOne({email})
if(!user)
{
throw new HttpException(401, 'User not found')
}
if(user.confirmed)
{
throw new HttpException(401, 'User already confirmed')
}
if(!user.confirmed)
{
const confirmJWT = jwt.verify(param, process.env.SECRET_JWT)
if(!confirmJWT)
{
throw new HttpException(200, 'Token invalid')
}
const result = await userModel.emailConfirmed(email)
}
res.send('Database updated.')
}
This is the code that generates the JWT and sends it in an email.
if(!user.confirmed)
{
const emailToken = jwt.sign(
{
email: user.email
},
process.env.SECRET_JWT,
{
expiresIn: '15m'
}
)
console.log(emailToken)
emailModel.verifyEmail(email, emailToken)
throw new HttpException(401, 'Email not confirmed')
}
I was wondering if there is any way I can use the just the token to find the email of the user or is that not possible with JWT?
export const verifyEmail = () => {
try
{
return API()
.post(`/api/confirm/:token`, {}, {
params: {
token: store.user.authToken
},
email: store.user.email
})
.then(({data: userData}) => {
console.log('worked')
})
}
catch(error)
{
console.log(error)
}
}
import { verifyEmail } from '../../services/authAPI'
import { useUserStore } from '../../stores/user'
const store = useUserStore()
export default {
data()
{
return {
email: store.user.email
}
},
methods: {
async handleSubmit()
{
try
{
const response = await verifyEmail(this.email)
}
catch(err)
{
console.log(err)
}
}
}
}
</script>
Basically you do not need to send the email in the body as already encoded the email into the JWT. Once you do const verifiedToken = jwt.sign(token, secret key) You can do verifiedToken.email to grab the email.

Why am I getting different response from my data when local and when on heroku?

I am working on an Application which i have also deployed in heroku. The issue is that when I login in using heroku, user is nested inside a data object. but when I work locally or use postman, user isnt nested.
Help Please.
I get this response on the deployed version.
data: {
user: {
email: "my_email"
name: "my_name"
role: "user"
_id: "6205807deeadcfa734f954f3".
}
status: "success"
token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjYyMDU4MDdkZWVhZGNmYTczNGY5NTRmMyIsImlhdCI6MTY0NDg0NTYyMCwiZXhwIjoxNjQ1NDUwNDIwfQ.YeWFNrN8rsLPJvvU8JQDwBVG4aBqqEuo7ssgLrR3O8M"
But when I log in locally, I get the response as
user: {
email: "my_email"
name: "my_name"
role: "user"
_id: "6205807deeadcfa734f954f3".
}
status: "success"
token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjYyMDU4MDdkZWVhZGNmYTczNGY5NTRmMyIsImlhdCI
For Heroku, the USER is nested inside data but for local host and postman, the user isnt nested.
My codes are:
exports.login = catchAsync(async (req, res, next) => {
const { email, password } = req.body
if (!email || !password) {
return next(new AppError('Please provide email and password!', 400))
}
const user = await User.findOne({ email }).select('+password')
if (!user || !(await user.comparePassword(password, user.password))) {
return next(new AppError('Incorrect email or password', 401))
}
createSendToken(user, 200, req, res)
})
These are my api codes
const createSendToken = (user, statusCode, req, res) => {
const token = signToken(user._id)
res.cookie('jwt', token, {
expires: new Date(
Date.now() + process.env.JWT_COOKIE_EXPIRES_IN * 24 * 60 * 60 * 1000
),
httpOnly: true,
})
user.password = undefined
res.status(statusCode).json({
status: 'success',
token,
user,
})
}
For my react, The function code is:
function request(path, { data = null, token = null, method = 'GET' }) {
return (
fetch(`${process.env.REACT_APP_API}${path}`, {
method,
headers: {
Authorization: token ? `Bearer ${token}` : '',
'Content-Type': 'application/json',
},
body:
method !== 'GET' && method !== 'DELETE' ? JSON.stringify(data) : null,
})
.then((response) => {
// If Successful
if (response.ok) {
if (method === 'DELETE') {
// If delete, nothing returned
return true
}
return response.json()
}
// If errors
return response
.json()
.then((json) => {
// Handle Json Error response from server
if (response.status === 400) {
const errors = Object.keys(json).map(
(k) => `${json[k].join(' ')}`
)
throw new Error(errors.join(' '))
}
throw new Error(JSON.stringify(json))
})
.catch((e) => {
if (e.name === 'SyntaxError') {
throw new Error(response.statusText)
}
throw new Error(e)
})
})
.catch((e) => {
// Handle all errors
toast(e.message, { type: 'error' })
})
)
}
The main sign in function
export function signIn(email, password) {
return request('/api/v1/auth/login', {
data: { email, password },
method: 'POST',
})
}
Then I import this into my auth context and execute it there
import {signIn as signInApi} from '../apis'
const AuthContext = createContext()
export const AuthProvider = ({ children }) => {
const [token, setToken] = useState(localStorage.getItem('token'))
const [user, setUser] = useState(
JSON.parse(localStorage.getItem('user'))
)
const [loading, setLoading] = useState(false)
const signIn = async (email, password, callback) => {
setLoading(true)
const res = await signInApi(email, password)
if (res.token) {
localStorage.setItem('token', res.token)
localStorage.setItem('user', JSON.stringify(res.user)) // This stores the user in localhost but returns undefined for user in the one deployed to heroku. I have to use
localStorage.setItem('user', JSON.stringify(res.data.user)) which now works on the deployed one but not on the local one
setToken(res.token)
setUser(res.user)
callback()
}
setLoading(false)
}
}
it seems the deployed version is using built in implementaion of createSendToken and not the one you provided. need to check your project structure.
in order to validate this change the function name and the call createSendToken to something else and you will find the issue

nodejs mongoose - how to check items in the database before login

In my project, I've different roles (seller/user/admin)and i want to check the role and redirect to specific page if they are seller for example.
I struggle on how i can check the role in Mongo DB before the login. My login page is basic email-password and submit button.
for my signup all is good, it's use the correct model and post it in the DB.
here are some pieces of my code:
(client model)
userSchema.statics.login = async function (email, password, role) {
const user = await this.findOne({ email });
if (user) {
const auth = await bcrypt.compare(password, user.password);
if (auth) {
return user;
}
throw Error("incorrect password");
}
throw Error("incorrect email");
};
const ClientModel = mongoose.model("client", userSchema, "users");
login controller:
module.exports.clientSignIn = async (req, res) => {
const { email, password } = req.body;
try {
const user = await LoginModel.login(email, password);
const token = createToken(user._id);
res.cookie("jwt", token, { httpOnly: true, maxAge });
res.redirect('/success');
} catch (err) {
console.log(err.message);
}
};
thanks in advance for your help, if you need more info please feel free to ask
Following #EAzevedo 's advice.
i just change my Controller
module.exports.clientSignIn = async (req, res) => {
const { email, password } = req.body;
try {
const user = await LoginModel.login(email, password);
const token = createToken(user._id);
res.cookie("jwt", token, { httpOnly: true, maxAge });
if (user.role == "client") {
res.redirect("/success");
} else if (user.role == "technicien") {
res.redirect("/success-technicien");
} else if (user.role == "superuser") {
res.redirect("/success-admin");
};
} catch (err) {
const errors = signInErrors(err);
res.status(200).json({ errors });
}
};
when you get the user , you should have field for the role ,
then check which role logged in and redirect him to where he needs to be

Make a login and registration system using Azure functions and Azure SQL server

I want to make a Dating application using node.js and javascript with Azure functions and an Azure sql server. I can create a user so it appears in my database, but how do I make a login system that "checks" if the users email and password is in the database and is correct.
This is what I have so far:
**Login.js:**
var form = document.getElementById("form")
form.addEventListener('submit', function(e) {
e.preventDefault()
var email = document.getElementById("email").value
var password = document.getElementById("password").value
fetch("http://localhost:7071/api/login", {
method: 'POST',
body: JSON.stringify({
email: email,
password: password,
}),
headers: {
"Content-Type": "application/json; charset-UTF-8"
}
})
.then((response) => {
return response.text()
})
.then((data) => {
console.log(data)
}).catch((err) =>{ // catcher fejl, hvis noget går galt
console.log("wuups: " + err)
})
})
**DB.js connect:**
function login (payload) {
return new Promise((resolve, reject) => {
const sql = 'SELECT * FROM [user] where email = #email AND password = #password'
const request = new Request(sql,(err,rowcount) =>{
if (err){
reject(err)
console.log(err)
} else if( rowcount == 0){
reject({messsage:"user does not exit"})
}
});
request.addParameter('email', TYPES.VarChar, payload.email)
request.addParameter('password', TYPES.VarChar, payload.password)
request.on('row',(colums) => {
resolve(colums)
})
connection.execSql(request)
return "you are now logged in"
});
}
module.exports.login = login;
You're on the right track. Consider an updated version of db.sql:
function login(payload, connection) {
return new Promise((resolve, reject) => {
const sql = 'SELECT * FROM [user] where email = #email AND password = #password'
const request = new Request(sql, (err, rowCount) => {
if (err) {
reject(err)
console.error(err)
}
else {
if (rowCount == 1) {
resolve(payload.email)
}
else {
reject('Invalid credentials')
}
}
});
request.addParameter('email', TYPES.VarChar, payload.email)
request.addParameter('password', TYPES.VarChar, payload.password)
connection.execSql(request)
});
}
Since we can infer a successful login from the amount of returned rows, we don't need access to the actual rows in the row callback.
However: as pointed out by Robert in the comments, storing passwords in plain text is a security concern (since access to the database immediately unveils user passwords).
Better approach
The better approach is to store hashed passwords instead. Imagine this simple user table schema in MSSQL:
CREATE TABLE [User] (
[Email] [varchar](max) NOT NULL UNIQUE,
[PasswordHash] [varchar(max)] NOT NULL
)
The login procedure will remain almost the same. Instead of comparing passwords we now compare hashed passwords. Without going into too much detail, you would usually use a library for this purpose (to handle salts, mitigate timing attacks, etc.). I chose bcryptjs for the example below:
var bcrypt = require('bcryptjs');
function login(email, password, connection) {
return new Promise((resolve, error) => {
const sql = 'SELECT * FROM [user] where email = #email' // Note that the password comparison no longer lives here
const request = new Request(sql, (err, rowCount) => {
if (err) {
reject(err)
}
})
request.addParameter('email', TYPES.VarChar, email)
let userRow = null
// This time we need the 'row' callback to retrieve the password hash
request.on('row', row => {
userRow = {
email = row[0].value,
passwordHash = row[1].value
}
})
// .. and the 'done' callback to know, when the query has finished
request.on('done', rowCount => {
if (rowCount == 0) {
reject('User not found')
}
else {
bcrypt.compare(password, userRow.passwordHash) // Password comparison
.then(passwordsMatch => {
if (passwordsMatch) {
resolve(email)
}
else {
reject('Invalid credentials')
}
})
}
})
connection.execSql(request)
})
}
And here's an example of how to create new users with this approach using the same library:
var bcrypt = require('bcryptjs');
const PASSWORD_SALT_ROUNDS = 10 // Learn more at ex. https://stackoverflow.com/questions/46693430/what-are-salt-rounds-and-how-are-salts-stored-in-bcrypt
function createNewUser(email, password, connection) {
return bcrypt.hash(password, PASSWORD_SALT_ROUNDS).then(passwordHash => {
const sql = 'INSERT INTO [user] (Email, PasswordHash) VALUES (#email, #passwordHash)'
const request = new Request(sql, err => {
if (err) {
error(err)
}
else {
resolve()
}
})
request.addParameter('Email', TYPES.VarChar, email)
request.addParameter('PasswordHash', TYPES.VarChar, passwordHash)
connection.execSql(request)
})
}
Consider this a pragmatic proposal to get started. Please note, that the code is illustrative, since I haven't actually executed it, and it is made under certain assumptions.

Select only records where domain based on user "permission"

Hello everyone I'd like to get some tips on how to solve my problem.
I got table where I stores user details like:
ID, FIRST_NAME, LAST_NAME EMAIL, PASSWORD, ROLE, DOMAIN
also got table named Aliases where I got
ID, DOMAIN_ID, DOMAIN, DESTINATION
In user table role means admin, moderator, user etc. and Domain for example is aaa.pl, bbb.pl. Also Aliases table got domain like aaa.pl, bbb.pl
I want to select from table only aliases where the domain is the same as assigned to the user.
So user X can sees only aliases Where is the same domain.
backend
This is my controller
public async aliasesListByDomain(req: Request, res: Response): Promise<void> {
const { domain } = req.params;
const aliasesListByDomain = await pool.query('SELECT * FROM virtual_aliases WHERE domain= ?', [domain]);
if (aliasesListByDomain.length > 0) {
res.json(aliasesListByDomain);
} else {
res.status(404).json({ message: "Alias doesn't exists" });
}
}
There is how I authenticate user
router.post('/authenticate', (req, res) => {
User.findOne({
where: {
email: req.body.email,
password: req.body.password
}
})
.then(user => {
if (user) {
let token = jwt.sign(user.dataValues, process.env.SECRET_KEY, {
expiresIn: 1440
})
res.json({ token: token })
} else {
res.send('User does not exist')
}
})
.catch(err => {
res.send('error: ' + err)
})
})
And now I have problem with my frontend.
This is my Service where I get all aliases ( no matter of domain )
getAliases(): Observable<Alias> {
return this.http.get(`${this.API_URI}/aliases`);
}
and there is my component where I get all Aliases
getAliases() {
this.aliasesService.getAliases().subscribe(
res => {
this.alias = res;
console.log(this.alias);
},
err => console.error(err)
);
}
Now how can I select only aliases based on users permission.
I tried something like this:
Service
getAliasesByDomain(domain: string): Observable<Alias> {
return this.http.get(`${this.API_URI}/aliases/${domain}`);
}
This is my auth service
to login
public login(user: TokenPayload): Observable<any> {
const base = this.http.post(`http://localhost:3000/users/authenticate`, user);
const request = base.pipe(
map((data: TokenResponse) => {
console.log(data);
if (data.token) {
this.saveToken(data.token);
}
return data;
})
)
return request;
}
and UserDetails
public getUserDetails(): UserDetails {
const token = this.getToken();
let payload;
if (token) {
payload = token.split('.')[1];
payload = window.atob(payload);
return JSON.parse(payload);
} else {
return null;
}
}
Should I in my component get usertoken then JSON.parse() it and get domain
details from logged user? Then send it to api?
What is best solution for this?
-Edit
I did something like this:
private getToken(): string {
if (!this.token) {
this.token = localStorage.getItem('usertoken');
}
return this.token;
}
getAliasesByDomain(){
const token = this.getToken();
let user;
if (token) {
user = token.split('.')[1];
user = window.atob(user);
user = JSON.parse(user);
console.log('user z from getAliasesByDomain: '+user.domain);
this.aliasesService.getAliasesByDomain(user.domain).subscribe(
res => {
console.log(res);
this.alias = res;
},
err => console.error(err)
);
}
}

Categories

Resources