Front-end (ReactJS) to Backend(NodeJS) Authentication - reactjs

New to React and quite confused...trying to access protected Node routes from React...how do I go about setting this authorization header upon login/registration? I have login / registration working fine for React...just kinda stuck here...
Of course I can set the header in Postman and it works...well in straight Node..
const JwtOptions = {
jwtFromRequest: ExtractJwt.fromHeader("authorization"),
secretOrKey: secret
};
const jwtAuth = new JwtStrategy(JwtOptions, function(payload, done) {
User.findById(payload.sub, function(err, user) {
if (err) {
return done(err, false);
}
if (user) {
done(null, user);
} else {
done(null, false);
}
});
});

With axios, you can add the Authorization header to your requests like this:
axios({ method: 'get', url: '/protected_route', headers: { Authorization: token } });

Related

How to send Basic Auth with axios

I'm trying to implement the following code, but something is not working. Here is the code:
var session_url = 'http://api_address/api/session_endpoint';
var username = 'user';
var password = 'password';
var credentials = btoa(username + ':' + password);
var basicAuth = 'Basic ' + credentials;
axios.post(session_url, {
headers: { 'Authorization': + basicAuth }
}).then(function(response) {
console.log('Authenticated');
}).catch(function(error) {
console.log('Error on Authentication');
});
It's returning a 401 error. When I do it with Postman there is an option to set Basic Auth; if I don't fill those fields it also returns 401, but if I do, the request is successful.
Any ideas what I'm doing wrong?
Here is part of the docs of the API of how to implement this:
This service uses Basic Authentication information in the header to establish a user session. Credentials are validated against the Server. Using this web-service will create a session with the user credentials passed and return a JSESSIONID. This JSESSIONID can be used in the subsequent requests to make web-service calls.*
There is an "auth" parameter for Basic Auth:
auth: {
username: 'janedoe',
password: 's00pers3cret'
}
Source/Docs: https://github.com/mzabriskie/axios
Example:
await axios.post(session_url, {}, {
auth: {
username: uname,
password: pass
}
});
The reason the code in your question does not authenticate is because you are sending the auth in the data object, not in the config, which will put it in the headers. Per the axios docs, the request method alias for post is:
axios.post(url[, data[, config]])
Therefore, for your code to work, you need to send an empty object for data:
var session_url = 'http://api_address/api/session_endpoint';
var username = 'user';
var password = 'password';
var basicAuth = 'Basic ' + btoa(username + ':' + password);
axios.post(session_url, {}, {
headers: { 'Authorization': + basicAuth }
}).then(function(response) {
console.log('Authenticated');
}).catch(function(error) {
console.log('Error on Authentication');
});
The same is true for using the auth parameter mentioned by #luschn. The following code is equivalent, but uses the auth parameter instead (and also passes an empty data object):
var session_url = 'http://api_address/api/session_endpoint';
var uname = 'user';
var pass = 'password';
axios.post(session_url, {}, {
auth: {
username: uname,
password: pass
}
}).then(function(response) {
console.log('Authenticated');
}).catch(function(error) {
console.log('Error on Authentication');
});
Hi you can do this in the following way
var username = '';
var password = ''
const token = `${username}:${password}`;
const encodedToken = Buffer.from(token).toString('base64');
const session_url = 'http://api_address/api/session_endpoint';
var config = {
method: 'get',
url: session_url,
headers: { 'Authorization': 'Basic '+ encodedToken }
};
axios(config)
.then(function (response) {
console.log(JSON.stringify(response.data));
})
.catch(function (error) {
console.log(error);
});
For some reasons, this simple problem is blocking many developers. I struggled for many hours with this simple thing. This problem as many dimensions:
CORS (if you are using a frontend and backend on different domains et ports.
Backend CORS Configuration
Basic Authentication configuration of Axios
CORS
My setup for development is with a vuejs webpack application running on localhost:8081 and a spring boot application running on localhost:8080. So when trying to call rest API from the frontend, there's no way that the browser will let me receive a response from the spring backend without proper CORS settings. CORS can be used to relax the Cross Domain Script (XSS) protection that modern browsers have. As I understand this, browsers are protecting your SPA from being an attack by an XSS. Of course, some answers on StackOverflow suggested to add a chrome plugin to disable XSS protection but this really does work AND if it was, would only push the inevitable problem for later.
Backend CORS configuration
Here's how you should setup CORS in your spring boot app:
Add a CorsFilter class to add proper headers in the response to a client request. Access-Control-Allow-Origin and Access-Control-Allow-Headers are the most important thing to have for basic authentication.
public class CorsFilter implements Filter {
...
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) servletResponse;
HttpServletRequest request = (HttpServletRequest) servletRequest;
response.setHeader("Access-Control-Allow-Origin", "http://localhost:8081");
response.setHeader("Access-Control-Allow-Methods", "GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH");
**response.setHeader("Access-Control-Allow-Headers", "authorization, Content-Type");**
response.setHeader("Access-Control-Max-Age", "3600");
filterChain.doFilter(servletRequest, servletResponse);
}
...
}
Add a configuration class which extends Spring WebSecurityConfigurationAdapter. In this class you will inject your CORS filter:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
...
#Bean
CorsFilter corsFilter() {
CorsFilter filter = new CorsFilter();
return filter;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(corsFilter(), SessionManagementFilter.class) //adds your custom CorsFilter
.csrf()
.disable()
.authorizeRequests()
.antMatchers("/api/login")
.permitAll()
.anyRequest()
.authenticated()
.and()
.httpBasic()
.authenticationEntryPoint(authenticationEntryPoint)
.and()
.authenticationProvider(getProvider());
}
...
}
You don't have to put anything related to CORS in your controller.
Frontend
Now, in the frontend you need to create your axios query with the Authorization header:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>
<body>
<div id="app">
<p>{{ status }}</p>
</div>
<script>
var vm = new Vue({
el: "#app",
data: {
status: ''
},
created: function () {
this.getBackendResource();
},
methods: {
getBackendResource: function () {
this.status = 'Loading...';
var vm = this;
var user = "aUserName";
var pass = "aPassword";
var url = 'http://localhost:8080/api/resource';
var authorizationBasic = window.btoa(user + ':' + pass);
var config = {
"headers": {
"Authorization": "Basic " + authorizationBasic
}
};
axios.get(url, config)
.then(function (response) {
vm.status = response.data[0];
})
.catch(function (error) {
vm.status = 'An error occured.' + error;
})
}
}
})
</script>
</body>
</html>
Hope this helps.
The solution given by luschn and pillravi works fine unless you receive a Strict-Transport-Security header in the response.
Adding withCredentials: true will solve that issue.
axios.post(session_url, {
withCredentials: true,
headers: {
"Accept": "application/json",
"Content-Type": "application/json"
}
},{
auth: {
username: "USERNAME",
password: "PASSWORD"
}}).then(function(response) {
console.log('Authenticated');
}).catch(function(error) {
console.log('Error on Authentication');
});
If you are trying to do basic auth, you can try this:
const username = ''
const password = ''
const token = Buffer.from(`${username}:${password}`, 'utf8').toString('base64')
const url = 'https://...'
const data = {
...
}
axios.post(url, data, {
headers: {
'Authorization': `Basic ${token}`
},
})
This worked for me. Hope that helps
An example (axios_example.js) using Axios in Node.js:
const axios = require('axios');
const express = require('express');
const app = express();
const port = process.env.PORT || 5000;
app.get('/search', function(req, res) {
let query = req.query.queryStr;
let url = `https://your.service.org?query=${query}`;
axios({
method:'get',
url,
auth: {
username: 'xxxxxxxxxxxxx',
password: 'xxxxxxxxxxxxx'
}
})
.then(function (response) {
res.send(JSON.stringify(response.data));
})
.catch(function (error) {
console.log(error);
});
});
var server = app.listen(port);
Be sure in your project directory you do:
npm init
npm install express
npm install axios
node axios_example.js
You can then test the Node.js REST API using your browser at: http://localhost:5000/search?queryStr=xxxxxxxxx
Ref: https://github.com/axios/axios
const auth = {
username : 'test',
password : 'test'
}
const response = await axios.get(yourUrl,{auth})
this is work if you use basic auth
I just faced this issue, doing some research I found that the data values has to be sended as URLSearchParams, I do it like this:
getAuthToken: async () => {
const data = new URLSearchParams();
data.append('grant_type', 'client_credentials');
const fetchAuthToken = await axios({
url: `${PAYMENT_URI}${PAYMENT_GET_TOKEN_PATH}`,
method: 'POST',
auth: {
username: PAYMENT_CLIENT_ID,
password: PAYMENT_SECRET,
},
headers: {
Accept: 'application/json',
'Accept-Language': 'en_US',
'Content-Type': 'application/x-www-form-urlencoded',
'Access-Control-Allow-Origin': '*',
},
data,
withCredentials: true,
});
return fetchAuthToken;
},
I used axios.interceptors.request.use to configure Basic auth credentials. I have a Backend Springboot(with SpringSecurity) application with a simple GET endpoint. The Frontend VueJs app and Backend runs on different ports.
axios.js
import axios from "axios";
const api = axios.create({
baseURL: "http://api_address",
timeout: 30000,
});
api.interceptors.request.use(
async (config) => {
const basicAuthCredentials = btoa("xxxx" + ":" + "xxxx");
config.headers.common["Authorization"] = "Basic " + basicAuthCredentials;
return config;
},
(error) => {
return Promise.reject(error);
}
);
export default api;
backend.js
import axios from "#/services/axios";
const BackendAPI = {
listUsers: async () => {
return axios({
url: '/users',
method: 'GET',
responseType: 'json',
});
},
};
export { BackendAPI };
Followed by the VUE component Users.vue
...
<script>
import { BackendAPI } from '#/services/backend';
export default {
name: "Users",
data() {
return {
usersList: [],
}
},
methods: {
async listUsers() {
const response = await BackendAPI.listUsers();
this.usersList = response.data;
},
},
};
</script>
The backend spring SecurityConfig.java with httpBasic as authentication and both cors and csrf disabled.
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/actuator/*")
.permitAll()
.anyRequest()
.authenticated()
.and()
.httpBasic()
.and()
.cors()
.disable()
.csrf()
.disable();
}
}

How to check user token in Angular?

I generated token with JWT using node and angular, and can't check if user is authorized.
Node:
module.exports.authenticate = function(req, res) {
var user = new User(req.body);
User.findOne({
username: req.body.username
}, function(err, user) {
if (err) throw err;
if (!user) {
res.json({ success: false, message: 'Authentication failed. User not found.' });
}
else if (user) {
if (user.password != req.body.password) {
res.json({ success: false, message: 'Authentication failed. Wrong password.' });
}
else {
var token = jwt.sign(user, config.secret, {
expiresIn: 60*60*24
});
res.json({
success: true,
token: token
});
}
}
});
};
Angular:
$http(req)
.then(function (response) {
console.log(response.data.success);
if(response.data.success) {
var user = localStorage.setItem('token', JSON.stringify(response.data));
token = localStorage.getItem('token');
// console.log('User info: ', JSON.parse(getuser));
// window.location = "/dashboard";
return response.data;
}
}, function (response) {
}
);
}
How can I check token when I change route?
And generically how can I use Token?
Angular ui-router provides $routeChangeStart event while you change a route. You can use it in the following way.
$rootScope.$on('$routeChangeStart', function (event, next, current){
//you can code here something to be run on changing routes
}
You might want to have a look here for detailed event documentation.
Regarding a more generic implementation , you can create a service to keep your token at the time of login or whenever you get it. Thereafter you can keep getting the token from the service for any future comparisons.
you should install "cookie-parser"
npm i cookie-parser
and go to index.js file and add
const cookieParser = require('cookie-parser');
app.use(cookieParser());
it works for me

how to use passport with angular node app?

I am working on a simple blog website based on angular.js + node.js and mongodb using express template.
I hit with $http from angular controller by POST method to a api named users.js where login is authenticated using passport.authenticate method.
I require passport-local login strategies in users.js.
But it's not working.here is angular login service code and node users api code.
Can anybody tell me how can use passport.js in angular and node?
angular routing through a service
app.service('Auth',function($location,$http,$localStorage){
var userLogin ;
return{
setLogIN:function(email,password){
$http({
method: 'POST',
url: '/users/login', //users.js having node routing.
data: {email:email, password:password},
})
node routing in user
router.post('/login',passport.authenticate('local', {
// use passport-local for authentication
successRedirect : '/profile',
failureRedirect : '/login',
failureFlash : true
}));
passport-local strategy
app.use(passport.initialize());
app.use(passport.session());
passport.use(new LocalStrategy(
function (username, password, done) {
User.findOne({username: username}, function (err, user) {
if (err) {
return done(err);
}
if (!user) {
return done(null, false, {alert: 'Incorrect username.'});
}
if (user.password != password) {
return done(null, false, {alert: 'Incorrect password.'});
}
return done(null, user);
});
}
));
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
User.findById(id, function(err, user) {
done(err, user);
});
});
function isAuthenticated(req,res,next){
if(req.isAuthenticated())return next();
res.redirect('/');
}
So I want to authenticate using passport, but use the client side templating/routing to keep the proper authentication.
Can someone please point me in the right direction? Or tell me if what I am doing is completely misguided?
edit : the error I AM getting with my code is it's not redirecting to profile page
TypeError: POST http://localhost:3000/users/login 500 Internal
Server Error
Not a valid User
i found solution to my question..
how to use passport with angular-nodejs routing.......
//angular js routing
$scope.userlogin=function(){
$http({
method:"post",
url:'/users/login',
data:{username:$scope.username,password:$scope.password},
}).success(function(response){
$scope.userData = response;
$localStorage.userData = $scope.userData;
console.log("success!!");
$location.path("/profile")
}).error(function(response){
console.log("error!!");
$location.path("/login")
});
}
i use POST method and hit to node (users.js) controller and get response from it. if user authentication is successful then it relocate to profile view otherwise remain on login view.
//add these two lines to app.js
// var app = express();
app.use(passport.initialize());
app.use(passport.session());
//node routing
// add passport-stretegies to users.js
passport.use(new LocalStrategy(function(username, password, done) {
user.findOne({username: username }, function(err, user) {
if (err) { return done(err); }
if (!user) {
return done(null, false, { message: 'Incorrect username.' });
}
if (user.password != password) {
return done(null, false, { message: 'Incorrect password.' });
}
return done(null, user);
// console.log(user)
});
}));
//passport serialize user for their session
passport.serializeUser(function(user, done) {
done(null, user.id);
});
//passport deserialize user
passport.deserializeUser(function(id, done) {
user.findById(id, function(err, user) {
done(err, user);
});
});
//router on same page
router.post('/login',passport.authenticate('local'),function(req,res){
res.send(req.user);
//console.log(req.user);
});
get a hit from angular side throught post method it use passport-local method for authentication if user is authenticated seccessfully then authenticated user is sent as response..
By default, LocalStrategy expects dictionary parameters to be named username and password.
If you want to use email instead of username, then you should define them in your strategy:
passport.use(new LocalStrategy({
usernameField: 'email',
passwordField: 'password'
},
function(username, password, done) {
// ...
}
));
For your case, it should be:
passport.use(new LocalStrategy({
usernameField: 'email',
passwordField: 'password'
},
function (username, password, done) {
User.findOne({username: username}, function (err, user) {
if (err) {
return done(err);
}
if (!user) {
return done(null, false, {alert: 'Incorrect username.'});
}
if (user.password != password) {
return done(null, false, {alert: 'Incorrect password.'});
}
return done(null, user);
});
}
));

angular.js http post to MVC async Task<ActionResult> web method fails

I Am trying to call an Async method in MVC controller (example Login) from an angular client and calls fail. I tried it with google postman tool also.
//
// POST: /Account/Login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (!ModelState.IsValid)
{
return View(model);
}
// This doesn't count login failures towards account lockout
// To enable password failures to trigger account lockout, change to shouldLockout: true
var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false);
switch (result)
{
case SignInStatus.Success:
return RedirectToLocal(returnUrl);
case SignInStatus.LockedOut:
return View("Lockout");
case SignInStatus.RequiresVerification:
return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
case SignInStatus.Failure:
default:
ModelState.AddModelError("", "Invalid login attempt.");
return View(model);
}
}
and here is the snippet of the angular service that is calling the Login method
var loginUser = function (email, password, returnUrl) {
var req = {
method: 'post',
url: '/Mysite/Login',
headers: {
'Content-Yype': undefined
},
data: {
model: {
Email: email,
Password: password,
RememberMe: false
},
returnUrl: returnUrl
}
};
return $http(req)
.then(function (response) {
return response.data;
}, function (reason) {
return reason;
});
};
the response throws me internal error with status 500.
Does angular.js support asynchronous calls to web methods?
Thanks and appreciate your help
If you are using the [ValidateAntiForgeryToken] decorator, the action needs a request verification token to be passed in the post data.
You could remove [ValidateAntiForgeryToken] but this would leave your action open to tampered requests.
The other option is to add an anti forgery token to the page and then pass its value in the request.
Your razor view will need a form with a token in it (Note: This is just a dummy form to allow the token to be added to the page).
#using(Html.BeginForm("Login", "ControllerName", FormMethod.Post, new { id = "verification-form"}) {
#Html.AntiForgeryToken()
}
In your javascript, you can then pass its value
var loginUser = function (email, password, returnUrl) {
var req = {
method: 'post',
url: '/Mysite/Login',
headers: {
'Content-Type': undefined
},
data: {
model: {
Email: email,
Password: password,
RememberMe: false
},
__RequestVerificationToken: $("#verification-form input[name=__RequestVerificationToken]").val(),
returnUrl: returnUrl
}
};
return $http(req)
.then(function (response) {
return response.data;
}, function (reason) {
return reason;
});
};

POST from angular.js doesn't work but directly from form works. Why?

I'm working on basic authentication for my project in node.js using passport.js and it's LocalStrategy method. It's even without password validation yet. Accounts are stored in MongoDB instance.
I was stuck for whole day when in course I'm going through instructor recommended binding form data to angular and sending $http.post() from there, like so:
$scope.signIn = function (username, password) {
$http.post('/login', {username: username, password: password})
.then(function (res) {
if(res.data.success) {
console.log('Logged in');
} else {
console.log('error logging in');
}
})
};
And here's the route for it:
app.post('/login', function (req, res, next) {
var auth = passport.authenticate('local', function (err, user) {
if(err) { return next(err); }
if(!user) { res.send({success: false, user: user}); }
req.login(user, function (err) {
if(err) { return next(err); }
res.render('index', { success: true, user: user });
});
});
auth(req, res, next);
});
Except it ALWAYS returned with { success: false, user: false }. After ton of googling I've decided to make a POST request directly from form:
JADE:
.navbar-right(ng-controller='navbarLoginCtrl')
form.navbar-form(action='/login' method='post')
.form-group
input.form-control(name='username' placeholder='username', ng-model='username' required)
.form-group
input.form-control(name='password' type='password', placeholder='password', ng-model='password' required)
button.btn.btn-default(type='submit' value="Submit") Sign in
as opposed to:
.navbar-right(ng-controller='navbarLoginCtrl')
form.navbar-form
.form-group
input.form-control(name='username' placeholder='username', ng-model='username' required)
.form-group
input.form-control(name='password' type='password', placeholder='password', ng-model='password' required)
button.btn.btn-default(ng-click='signIn(username, password)') Sign in
Submit approach actually works but i'd like to keep things clean and do it with angular.
How can I do it?
Other passport.js components for reference:
var User = mongoose.model('User');
passport.serializeUser(function (user, done) {
if (user) {
done(null, user._id);
}
});
passport.deserializeUser(function (id, done) {
User.findOne({_id: id}).exec(function (err, user) {
if(user) {
return done(null, user);
} else {
return done(null, false);
}
});
});
passport.use(new LocalStrategy(
function (username, password, done) {
User.findOne({username: username}, function (err, user) {
if (user) return done(null, user);
else return done(null, false);
});
}
));
You should check what your browser send.
Your broswer form send data in the form username=&password=, angular post them in JSON {username:, password:} and the Content-Type header is different.
If you want to do the same in angular :
var headers={ 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'};
return $http.post(BackEndpoint+'/login','username='+username+'&password='+password,
{headers:headers}).then(function(result){
});
This is what i use against spring authentication.

Resources