I'm trying to setup a Vue2js app with node.js/express, using JWT authentication.
When signing in token is generated (with bearer) and stored in the client-side (Vuex) successfully.
When reload token somehow dissapers from header and I don't know why?
So when calling fetchAccountFromToken function from helpers/token.js I have below error on the server side:
"TypeError: Cannot read property 'split' of undefined"
helpers/token.js
export function fetchAccountFromToken(token) {
return JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString('utf-8'))['user']
}
And I have this code in server.js
app.post('/login', async (req, res) => {
if (req.body == null) {
res.status(401).json({ error: 'Invalid login. Please try again' })
} else {
const userService = new UserService()
const token = await userService.loginUser({
email: req.body.email,
password: req.body.password
})
console.log(token)
if (token) {
res.json({ token })
} else {
res.status(401).json({ error: 'Invalid login. Please try again' })
}
}
})
UserService.js
export default class UserService {
async loginUser(loginUserRequest) {
const { email, password } = loginUserRequest
const userRepository = new UserRepository()
const userDto = await userRepository.getUserByEmail(email)
if (userDto.email === email && userDto.password === password) {
let user = {
id: userDto.id,
email: userDto.email,
firstName: userDto.firstName,
lastName: userDto.lastName,
role: userDto.role
}
return jwt.sign({ user }, 'the_secret_key') //secret key je za validacijo tokena
}
return null
// return res.status(401).json({ error: 'Invalid login. Please try again.'}) // NEEDS to send error if credentials don't match !!!! //
}
UserRepository.js
export default class UserRepository {
async getUserByEmail(email) {
let dbContext = new DbContext()
try {
const query = 'SELECT id, email, password, firstName, lastName, role FROM accounts WHERE email = ?'
const users = await dbContext.query(query, [email])
return users[0]
} finally {
dbContext.close()
}
}
And I have this code in the VueX store module user.js:
export const state = {
user: null
}
export const mutations = {
SET_USER_DATA(state, data) {
console.log('logging in with data data:', data)
let { token } = data
localStorage.setItem('token', token)
let tokenPayloadJson = atob(token.split('.')[1])
let tokenPayload = JSON.parse(tokenPayloadJson)
let user = tokenPayload.user
state.user = user
localStorage.setItem('user', JSON.stringify(user))
console.log('called set user data')
axios.defaults.headers.common['Authorization'] = `Bearer ${data.token}`
},
CLEAR_USER_DATA() {
localStorage.removeItem('token')
localStorage.removeItem('user')
location.reload()
}
}
export const actions = {
login({ commit }, credentials) {
return axios
.post('//localhost:3000/login', credentials)
.then(({ data }) => {
commit('SET_USER_DATA', data)
})
},
fetchUser(id) {
return AccountService.getUser(id)
.then(response => {
return response.data
})
},
logout({ commit }) {
commit('CLEAR_USER_DATA')
}
}
export const getters = {
loggedIn(state) {
return !!state.user
}
}
I don't see storing the token to VueX, just saving it to localStorage. Additionally I don't see how you are reading it from it (neither localStorage nor VueX store). You can load it from localStorage when initializing the store like this:
export const state = {
user: localStorage.getItem('user'),
token: localStorage.getItem('token')
}
Related
I'm using this example to create a simple authentication with Nextjs https://github.com/vvo/next-iron-session/tree/master/examples/next.js
but instead of fetching the user JSON object from Github (as the example does) im trying to do it from my mongodb database where i have some users.
I did this on my login.js file:
import fetchJson from "../../lib/fetchJson";
import withSession from "../../lib/session";
import { withIronSession } from "next-iron-session";
import { connectToDatabase } from "../../util/mongodb";
export default withSession(async (req, res) => {
const { db } = await connectToDatabase();
const { username } = await req.body;
const foundUser = await db.collection("users").findOne({"userName": username});
console.log(foundUser) // <--- this returns the user object on console just fine
const url = `http://localhost:3000/api/user/${foundUser}`;
try {
const { userName, email } = await fetchJson(url);
const user = { isLoggedIn: true, userName, email }
req.session.set("user", user);
await req.session.save();
res.json(user);
} catch (error) {
const { response: fetchResponse } = error;
res.status(fetchResponse?.status || 500).json(error.data);
}
});
And i have this code on my /api/user.js file:
import withSession from "../../lib/session";
import { connectToDatabase } from "../../util/mongodb";
export default withSession(async (req, res) => {
const user = req.session.get("user");
if (user) {
const { db } = await connectToDatabase();
const foundUser = await db.collection("users").findOne({"userName": user.userName, "email": user.email});
console.log("useri pi te user.js " + foundUser)
// in a real world application you might read the user id from the session and then do a database request
// to get more information on the user if needed
res.json({
isLoggedIn: true,
...user,
});
} else {
res.json({
isLoggedIn: false,
});
}
});
But i get "invalid json response body at http://localhost:3000/api/user/[object%20Object] reason: Unexpected token < in JSON at position 0" error even though i get the user object printed in the console just fine.
Any help would be appreciated!
Whenever I try to login with the correct user and correct password everything is fine, but whenever I try to login with a not existing user or a mistaken password I just get the same mistake which is:
{
"name": "NotAuthenticated",
"message": "Invalid login",
"code": 401,
"className": "not-authenticated",
"errors": {}
}
The expected outcome is to show: user doesn't exist. Or for example: given user and password doesn't match
here is what I'm doing on my code
var username = "givenUsername"
var password = "givenPassword"
client.authenticate({
strategy: 'local',
username, password
}).then((authResponse)=>{
console.log(authRersponse)
}).catch((err)=>{
console.error(err)
})
This is not done by default because it would allow an attacker to guess which email addresses or user names are registered on your system. You can always customize the local authentication strategy to throw the errors you would like, for example by overriding findEntity and comparePassword:
const { AuthenticationService, JWTStrategy } = require('#feathersjs/authentication');
const { LocalStrategy } = require('#feathersjs/authentication-local');
const { NotAuthenticated } = require('#feathersjs/errors');
class MyLocalStrategy extends LocalStrategy {
async findEntity(username, params) {
try {
const entity = await super.findEntity(username, params);
return entity;
} catch (error) {
throw new Error('Entity not found');
}
}
async comparePassword(entity, password) {
try {
const result = await super.comparePassword(entity, password);
return result;
} catch (error) {
throw new Error('Invalid password');
}
}
}
module.exports = app => {
const authService = new AuthenticationService(app);
authService.register('local', new MyLocalStrategy());
// ...
app.use('/authentication', authService);
}
I'm trying to make an HTTP POST and then check the response to see if it fails or succeeds.
The HTTP call looks like this :
doLogin(credentials) {
var header = new Headers();
header.append('Content-Type', 'application/x-www-form-urlencoded');
var body = 'username=' + credentials.username + '&password=' + credentials.password;
return new Promise((resolve, reject) => {
this.http.post(this.url, body, {
headers: header
})
.subscribe(
data => {
resolve(data.json());
},
error => {
resolve(error.json());
}
);
});
}
And the call of this function is the following :
data: Object;
errorMessage: Object;
login($event, username, password) {
this.credentials = {
username: username,
password: password
};
this._loginService.doLogin(this.credentials).then(
result => {
this.data = result;
console.log(this.data);
},
error => {
this.errorMessage = <any>error;
console.log(this.errorMessage);
});
}
On Chrome console, the data is the following :
Object {status: "Login success", token: "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJjcmlzdGkiLCJ1c2VyS…blf1AzZ6KzRWQFNGXCrIeUHRG3Wrk7ZfCou135WmbVa15iYTA"}
How can I access the status in Angular 2? Because if I'm trying to access this.data.status, it's not working.
Should I create a class with the status and token properties?
To answer your question, you can use the response.okboolean that's available in the subscription of the observable from the http.
So based on your code you could pass the data object straight to the promise and inspect data.ok before parsing the data.json.
//...
return new Promise((resolve, reject) => {
this.http.post(this.url, body, {
headers: header
})
.subscribe(resolve,
error => {
reject(error.json());
}
);
});
// then you would have something like this:
this._loginService.doLogin(this.credentials).then(
result => {
if (result.ok) {
this.data = result;
console.log(this.data);
}
},
error => {
this.errorMessage = <any>error;
console.log(this.errorMessage);
})
SUGGESTION
Now, I would recommend getting rid of the promise, as I believe you don't really need it. whoever is consuming your service can just subscribe to the observable returned by the http post, like so:
doLogin(credentials) {
let header = new Headers();
header.append('Content-Type', 'application/x-www-form-urlencoded');
var body = 'username='+credentials.username+'&password='+credentials.password;
return this.http.post(this.url, body, { headers: header });
}
Then, when logging in:
login($event, username, password) {
this.credentials = {
username: username,
password: password
};
this._loginService.doLogin(this.credentials).subscribe(response => {
if (response.ok) { // <== CHECK Response status
this.data = response.json();
console.log(this.data);
} else {
// handle bad request
}
},
error => {
this.errorMessage = <any>error;
console.log(this.errorMessage);
});
}
Hope this helps!
You could do it like this:
data: Object;
errorMessage: Object;
login($event, username, password) {
this.credentials = {
username: username,
password: password
};
this._loginService.doLogin(this.credentials).then(
(result: any) => {
this.data = result;
console.log(this.data);
console.log(this.data.status);
},
error => {
this.errorMessage = <any>error;
console.log(this.errorMessage);
});
}
Set the result to type any. That way you'll be able to access the status, however you could create a class and use rxjs/map within your service to populate the class if you so desire.
I have one application which include login and home component,
login.service.ts
let body = JSON.stringify(data);
console.log("logged in user",body);
return this._http.post('http://localhost:8080/api/user/authenticate', body, { headers: contentHeaders })
.map(res => res.json())
.map((res) => {
var token1:any = res;
console.log(token1);
if (token1.success) {
localStorage.setItem('auth_token', token1.token);
this.LoggedIn = true;
}
return res.success;
});
}
isLoggedIn() {
return this.LoggedIn;
}
in this service i am getting token in variable token1 and isLogged method contain
constructor(private _http: Http) {
this.LoggedIn = !!localStorage.getItem('auth_token'); }
Login.component.ts
login(event, username, password)
{
this.loginService.login(username, password)
.subscribe(
response => {
this.router.navigate(['/home']);
alert("login successfull");
},
error => {
alert(error.text());
console.log(error.text());
}
);
From this login i can able to authenticate and and its routing to home component,
Home.serice.ts
getClientList()
{
let headers = new Headers();
headers.append('Content-Type', 'application/json');
let authToken = localStorage.getItem('auth_token');
headers.append('X-auth-Token', 'authToken')
return this._http.get('http://localhost:8080/api/v1/client/list?isClient=Y', {headers})
.map(res => res.json())
}
Home.component.ts
onTestGet()
{
this._httpService.getClientList()
.subscribe(
data => this.getData = JSON.stringify(data),
error => alert(error),
() => console.log("finished")
);
}
now question is how can i access that token in home component which is in token1 varible(login) i have tired to getitem token.but i am getting token null.please anybody help me.
thanks in advance
localStorage.getItem('auth_token')
This should work, but you are getting null, because lifecycle of the data different.
I suggest you to use Subject construction for this purpose, especially you already have service with data.
Example:
loginInfo$ = new Subject();
private _logininfo = null;
getLoginData () {
if(!_logininfo) {
this.http..... { this._loginInfo = data;
this.loginInfo$.next(data); }
return this.loginInfo$.first()
}
else return Observable.of(this._logininfo);
}
So now, your service at the same time storage of data and handler for missing login.
I am using socket.io and mysql (node server)
But I am not successful in delete function.
Here's what I have and what I've tried so far
io.on('connection', (socket) => {
connection.query("SELECT * FROM `messages`", (err, data) => {
for(let x in data) socket.emit('message', { id: data[x].message_id, text: data[x].message })
})
socket.on('disconnect', () => {
// console.log('user disconnected');
})
socket.on('add-message', (message) => {
addMessage(message, (res) => {
if(res) io.emit('message', { type: 'new-message', text: message});
})
});
socket.on('delete-message', (id) => {
connection.query("DELETE FROM `messages` WHERE `message_id` = '"+ id +"'");
io.emit('message', { type: 'delete-message', id: id }) // broadcast that something has changed
})
})
Angular2 service
import { Subject } from 'rxjs/Subject'
import { Observable } from 'rxjs/Observable'
import * as io from 'socket.io-client'
export class ChatService {
private url = 'http://localhost:5000'
private socket;
sendMessage(message) {
this.socket.emit('add-message', message);
}
getMessages() {
let observable = new Observable(observer => {
this.socket = io(this.url);
this.socket.on('message', (data) => {
observer.next(data);
});
return () => {
this.socket.disconnect();
};
})
return observable;
}
deleteMessage(id) {
this.socket.emit('delete-message', id);
}
}
Component
export class AppComponent implements OnInit, OnDestroy {
messages = []
connection;
message: any;
constructor(private chatService: ChatService){ }
sendMessage(): void {
this.chatService.sendMessage(this.message);
this.message = '';
}
ngOnInit() {
this.connection = this.chatService.getMessages().subscribe(message => {
this.messages.push(message);
})
}
ngOnDestroy() {
this.connection.unsubscribe();
}
deleteData(id): void {
for(var i = 0; i < this.messages.length; i++) {
if(this.messages[i].id == id) {
this.messages.splice(i, 1)
this.chatService.deleteMessage(id)
break;
}
}
}
}
Problem of what I have tried:
For deleteData(),
The user who clicked the delete button will have the desired view. But for other users, they must refresh for updated data.
Any help would be appreciated. Thanks.
First, keep in mind that you need to store all of your data into the array messages.
The challenging part is the message_id. Since you can't put a value on it. Assuming that it has an auto_increment. We need to add another table column which will have a unique value.
For my example, I will use message_identifier
The table will have (message_id, message_content, message_identifier)
To keep this short. message_identifier will just have time that converted to milliseconds(I believe). You must create a method that will make it completely different.
On your SERVER
Getting previous messages
connection.query("SELECT * FROM `messages`", (err, data) => {
for(let x in data) socket.emit('message', { type: 'get-messages', message: data[x].message, identifier: data[x].identifier })
}
Adding message
socket.on('add-message', function(message, identifier) {
connection.query("INSERT INTO `messages` (`message_content`, `message_identifier`) VALUES ('"+ message +"', '"+ identifier +"')", (err) => {
if(!err) io.emit('message', { type: 'new-message', message: message, identifier: identifier })
})
})
Deleting message
socket.on('delete-message', function(identifier) {
connection.query("DELETE FROM `messages` WHERE `message_identifier` = '"+ identifier +"'", (err) => {
if(!err) io.emit('message', { type: 'delete-message', identifier: identifier })
});
})
The logic will be on the component. You just need to listen for 'message' and identify through the type that request is passing.
So, here it goes:
Importing socket.io and observable and declaring socket on your component.
import * as io from 'socket.io-client'
import { Observable } from 'rxjs/Observable'
private socket = io(/* url of server */); // inside AppComponent
On your class AppComponent. You need to listen to 'message'
let data$ = new Observable(observer => {
this.socket.on('message', (data) => {
if(data.type == 'get-message' || data.type == 'new-message') {
observer.next({ message: data.message, identifier: data.identifier })
} else if(data.type == 'delete-message') {
for(let i = 0; i < this.messages.length; i++){
if(parseInt(this.messages[i].identifier) == data.identifier){
this.messages.splice(i, 1);
break;
}
}
}
console.log(data)
})
})
data$.subscribe(value => {
this.messages.push(value);
})
You can put this on ngOnInit or constructor. I believe it should work either of this two.
On your SERVICE
Just remove getMessages since we're handling it on component.
Hope it helps. Cheers!
You are sending a message from the client to your nodejs server to delete the message. What you are forgetting at the server side however, is to update al the other clients that something has changed. In your 'socket.on("delete-message")', you should also be sending a message to all connected users to notify them, something has changed. You can do that similarly to the add message:
io.emit('message', { type: 'delete-message', id: id});
Btw: Checkout ngrx/store. It's a Redux implementation for angular 2. If you are working with ngrx/store you define actions. Actions are meant to update the client side state. If you were using this, you could just define an action 'DELETE_MESSAGE' and send this action through your socket from server to client. The client would just dispatch this action to ngrx and your UI would update nicely :).