can't get access to angular.js $cookies object - angularjs

I am using the angular.js and csrf (Cross-site request forgery).
I have added the angular cookie file.
script src="angular-cookies.js"
And then, to load it in the application:
angular.module('myApp', ['ngCookies']);
It’s really easy to add the CSRF token in the headers of the $http service. We just need to configure it into the run process of the application, requiring the $cookie and $http services.
.run(function ($http, $cookies) {
$http.defaults.headers.post['x-csrf-token'] = $cookies._csrf;
});
in the browser debug console, I can find the cookie is existing.
However,when the page is loaded, $cookies outputs undefined, it is strange the $cookies object itself is undefined, but obviously I have loaded the cookie.js and inject the module into the application
This is on the node.js server side, which how I set a CSRF token on the cookie
app.use(require('csurf')());
app.use(function(req, res, next){
res.locals.token = req.csrfToken();
res.cookie('XSRF_TOKEN', req.csrfToken(),{ maxAge: 900000, httpOnly: false });
next();
});
Thanks for the reminding, I changed the
res.cookie('XSRF_TOKEN', req.csrfToken(),{ maxAge: 900000 });
to
res.cookie('XSRF_TOKEN', req.csrfToken(),{ maxAge: 900000, httpOnly: false });
so the cookie is readable from the browser.

According to angularJS documentation
Your server needs to set a token in a JavaScript readable session cookie called XSRF-TOKEN on the first HTTP GET request.
This cookie must be readable with Javascript, and ideally sent with the index.html page.
When this is done, you should see it in your browser (options / cookies ...)
At this point, you should access it in your angular code.
If this is not the case, may you should try to access it with pure Javascript
_getCookie = function(cname) {
var name = cname + "=";
var ca = document.cookie.split(';');
for(var i=0; i<ca.length; i++){
var c = ca[i].trim();
if (c.indexOf(name)==0) return c.substring(name.length,c.length);
}
return "";
};
getCookie("XRSF_TOKEN");

Your cookie is probably being set as HttpOnly = true which will set the cookie but will not allow the client to view it therefore it comes back as undefined. You will need to make sure this flag is set to false.

Related

Enabling browser cookies with Swagger-js

I am using swagger-js/swagger-client (https://github.com/swagger-api/swagger-js) in an AngularJs SPA to send REST request to a nodeJS server using the TryItOut Executor.
I am using PassportJS for authentication. When I use Angular's $http service with the withCredentials property set to true:
$http({
method: 'GET',
withCredentials: true,
url: 'http://someUrl/someRestPath/' })
, everything works OK - browser cookies are properly set, and every further request is successfully authenticated.
Switching to swagger-js Executor, I cannot get authenticated properly since cookies are not being set. I have tried the following:
var swaggerClient = new SwaggerClient(specUrl)
.then(function (swaggerClient) {
swaggerClient.http.withCredentials = true; // this activates CORS, if necessary
as suggested in the documentation but this has no effect. Although I receive credentials when I authenticate with passport, further requests are unauthorized as cookies are not being set.
Left: cookies not being sent; Right: cookies being set
How to enable this behavior using swagger-js?
I was able to find a solution. The execute method of the Swagger-js client allows us to define our own http method. I simply used angular's $http service with the withCredentials set to true globally in the .config
import Swagger from 'swagger-client';
in .config:
angular.module('app', []).config(['$httpProvider',function ($httpProvider) {
$httpProvider.defaults.withCredentials = true;
}
And then when you create your swagger client:
Swagger({ url: 'some swagger specification (yaml/json)' }).then(client => {
client.execute({ http: $http, operationId: operationId, parameters: parameters});

Angular.js SPA security with ASP.NET MVC and WebApi

I'm building a SPA using Angular.js and ASP.NET and I would like to know what is the best way to secure it.
Here is what I need :
I would like to use MVC framework to hide my application only to logged users. So the first thing that users will do before launching the SPA will be to log into the website using a simple login form.
When the Angular app will be launched, it will communicate with my ApiController using REST requests.
I also want my user to be logged out automatically after 20 minutes of inactivity.
I know that REST is supposed to be stateless... but I can't figure how to implement all I need without sessions...
But on the other side, I want to be able to use my WebAPI with a future mobile application. I will have to use Tokens for the authentication on this application.
What is the best way for me to achieve that kind of authentication?
Thanks for your time!
I developed an entire security layer with the same conditions as yours following those very well explained in this post here.
BTW, the token will expire automatically after 20 minutes because when you create it you will set it's expiration date immediately; every time you're going to make a request, the system will check the token exp date with the current date, refusing your token if the time passed. For example this a tipical oauth server configuration with token and refresh token settings:
internal static OAuthAuthorizationServerOptions GetAuthorizationServerOptions(IComponentContext scope)
{
OAuthAuthorizationServerOptions oAuthServerOptions = new OAuthAuthorizationServerOptions
{
AllowInsecureHttp = true,
ApplicationCanDisplayErrors = true,
TokenEndpointPath = new PathString(Constants.PublicAuth.OAUTH_TOKEN_PATH),
AuthorizeEndpointPath = new PathString(Constants.ExternalAuth.AUTH_ENDPOINT),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(Constants.PublicAuth.TOKEN_EXPIRATION_IN_MINUTES),
Provider = scope.Resolve<AuthorizationServerProvider>(),
AccessTokenFormat = new CustomJwtFormat(),
RefreshTokenProvider = scope.Resolve<SimpleRefreshTokenProvider>()
};
return oAuthServerOptions;
}
The refresh token is also very useful, but you have to manage the token replacement by yourself; for example in our application we pass every API call through a single service that, if the server responds 401 (unauthorized), it will try to request a new token using the refresh token and then it will try the same call again. Only after the second failure you'll be redirected to the login page.
For example:
function executeCallWithAuth(method, url, payload, params) {
var defer = $q.defer();
debug.logf('{0}: {1}', method.toUpperCase(), url);
$http({ method: method, url: url, data: payload, headers: createHeaders(), params: params }).then(
function(results) { defer.resolve(results); },
function(error) {
if (error.status !== 401) defer.reject(error);
else {
debug.warn(`Call to: ${method}:${url} result in 401, try token refresh...`);
auth.refreshToken().then(
function() {
debug.warn('Token refresh succesfully, retry api call...');
$http({ method: method, url: url, data: payload, headers: createHeaders() }).then(
function(results) { defer.resolve(results); },
function(errors) { defer.reject(errors); });
},
function(tokenError) {
debug.warn('Token refresh rejected, redirect to login.');
$state.go('login');
defer.reject(tokenError);
});
}
});
return defer.promise;
}
and
function createHeaders() {
var headers = {
};
var authData = storage.get('authorizationData');
if (authData) {
headers.Authorization = 'Bearer ' + authData.token;
}
return headers;
}
Using Angular the best way to secure a route is "do not create a route". Basically, you need to load the user profile, and only after that you will create the routes only to the pages he can navigate to. If you don't create the route for a page you don't need to secure that page: Angular will automatically send the user to a 404.
I would secure your WebAPI calls with OAuth2 (you can even use the built in Identity 2.0 provider that comes baked in with it). Keep your WebAPI stateless, use SSL (consider a filter to force it), and use the [Authorize] tags to secure you services. On the MVC side, this will have to maintain state and you will want to have the login form get an OAuth2 token from your WebAPI layer and pass that down into Angular. Set the expiration on this to 20 minutes. You can also use the cookies authentication model here since it will need to be stateful on the MVC side, but all ajax calls made to the WebAPI layer by Angular will need to pass the OAuth2 token as a bearer token in the Authorization request header.

AngularJS html5Mode fallback in IE. Express server needs to know route

I'm using html5Mode=true with AngularJS routing. Works fine. When I access the site with IE, Angular routing falls back to Hashbang URI's like http://example.com/#!/route-name. That's all fine. Except in Express I need to know the route, because it tells me which html file to serve from Express. The # part of the url is not sent to the server.
My question is, how can I let my server know which AngularJS route is being requested with hashbang routing in IE? I was thinking of configuring Angular somehow to send the route as an http header that I can read in express, but I don't know how to do that. Any ideas?
Update: Based on feedback I got, let me tell you that the site has templates. One for the homepage and one for all the other pages. They are both pretty different. Based on the route, the server needs to know when to serve the file for the homepage and when to serve the file for the other pages.
Just to be clear: this won't send the hashbang on the main page request. Based on the HTTP specifications there is no way of doing that.
You can send it on subsequent AJAX requests, but this won't really help you with solving this problem. There is no solution other than not supporting browsers that don't support html5mode.
Browsers won't send the hashbang to the server automatically, so yes you will need to send it manually. Using a header is a good option.
Take a look at $httpProvider.interceptors in the docs.
You can register a request interceptor for $http (AngularJS uses it internally for any AJAX request and so should you) that attaches a custom header. You can then set that header value to be the actual hashbang using $location.path():
var app = angular.module('MyApp',[]);
app.factory('httpInterceptor', function($q, $location) {
return {
'request': function(config) {
if(config.url.indexOf('product')) {
config.headers.Hashbang = $location.path();
}
return config;
}
};
});
app.config(function($httpProvider) {
$httpProvider.interceptors.push('httpInterceptor');
});
app.controller('Ctrl', function($scope, $http) {
$http({method: 'GET', url: '/echo/json/'}).
success(function(data, status, headers, config) {
console.log(data);
});
});
Here's the fiddle. You can test it like so. Check out the sent headers in developer tools.
Notice that I'm not testing with a true hashbang (#!) but just a hash because the fiddle does not allow it.
I had a similar problem where I needed the parameters of the URL before they were removed by the redirect. As the URL is redirected in angular from the original one the previous URL is in the referer header.
So I used this express 3.x middleware to rewrite the original parameters from the referer to the query on requests to the root URL:
var _ = require('lodash');
var url = require('url');
var qs = require('querystring');
app.use(function(req, res, next) {
if (req.url === '/' && req.headers && req.headers.referer) {
var referer = url.parse(req.headers.referer);
// rewrite original parameters
_.forEach(qs.parse(referer.query), function(value, key) {
req.query[key] = value;
});
// do something with pathname
console.log(referer.pathname);
}
});
You could do the same in your case for the path, that is in referer.pathname.
There seems no client-side answer as per the answer of Sergiu Paraschiv. So I have investigated a server-side solution that requires only one thing on the client. Make sure you always links as you would do in html5Mode which is linking to /xxxxx and not /#!/xxxxx.
Then In html5Mode and IE9 and lower what happens is that AngularJS redirects everything /xxxxx to /#!/xxxxx. In Express this makes the url / and the referer /xxxxx. This is something that you can check for quite easily.
If you want to cater for IE8 and lower to, it's unfortunate that Angular uses window.location.href in this fall back scenario redirecting to /#!/xxxxx. See github / angular.js / src / ng / browser.js line 169.
Using window.location.href causes IE8 and lower to not send the referer.
Here's a server-side (Express 3.x) solution that resolves the Angular hashbang route from the referer value or a previousUrl session variable for IE8 and lower.
app.use(function(req, res, next) {
if (req.url.match(/\/api/))
return next(); // /api/something should proceed to next in middleware
console.log(req.url, req.headers)
//IE does not support html5Mode so /xxxxx is redirected to /#!/xxxxx
//effectively making the url / and the referer /xxxxx
//I check for that here and if xxxxx is not home I present page.html
if (req.headers['user-agent'] &&
req.headers['user-agent'].match(/MSIE ([7-8]{1}[.0-9]*)/) &&
req.url.match(/^\/[\w-]+$/)
) {
//However IE8 does not report the referer when doing a redirect using window.locarion.href
//so I store the initially requested url /xxxxx in session...
req.session.previousUrl = req.url;
//console.log('IE8 saving the url here', req.session.previousUrl);
}
if (req.headers['user-agent'] &&
req.headers['user-agent'].match(/MSIE ([7-8]{1}[.0-9]*)/) &&
!req.headers['referer'] &&
req.url === '/' &&
req.session.previousUrl &&
req.session.previousUrl !== '/home'
) {
//continuation on the IE* referer story (aka the next redirected request)...
//... and checked for the url stored in session here ;)
return res.sendfile('public\\page.html');
}
if (req.headers['user-agent'] &&
req.headers['user-agent'].match(/MSIE ([7-9]{1}[.0-9]*)/) &&
req.url === '/' &&
req.headers['referer'] &&
req.headers['referer'].match(/^https?:\/\/(?:www)?\.example\.com\/(?!home$)([\w-]+)$/)
) {
return res.sendfile('public\\page.html');
}
if (req.url.match(/\/home|\/$/))
return res.sendfile('public\\home.html'); //for the home pages we're going to use home.html
res.sendfile('public\\page.html'); //for everything else we're going to use page.html
});
I'm sure that there are scenario's where this fails, but it worked for me in my tests and if it fails, that will only fail for IE9- browsers (as per the regexp's).

Login by facebook in angular app with loopback backend

I'm making an angular application with strongloop loopback backend.
Also I integrating a third party login by facebook using loopback-passport module.
everything was fine in loopback-example-passport and everything is fine in my app right before the moment of redirecting to my app. User and Access-token created.
the code:
app.get('/auth/login', ensureLoggedIn('/#login'), function(req, res, next) {
console.log('LOOGED IN!!');
console.log(req.user);
res.redirect('/#auth/login');
});
works fine. But i can't understand. how to give authenticated state to my angular application.
i tried to make a controller to route '/#auth/login':
.controller('AuthCalbackCtrl', function($scope, $cookies, $location, AppAuth, $http, User, LoopBackAuth) {
//analogue of User.login responce interceptor
LoopBackAuth.currentUserId = $cookies['userId'] || null;
LoopBackAuth.accessTokenId = $cookies['access-token'] || '';
LoopBackAuth.rememberMe = false;
LoopBackAuth.save();
//asking for currentUser
User.getCurrent(function(user) {
console.log('ser.getCurrent ', user);
});
$location.path('/');
})
This code makes a request GET /api/users/2 but receives 401 error.
If I tweak the file /loopback/lob/models/user.js setting permission:
principalType: ACL.ROLE,
// principalId: Role.OWNER,
principalId: Role.EVERYONE,
permission: ACL.ALLOW,
property: "findById"
Then the request GET /api/users/2 receives 200 and everything ok.
I'm a little confused. I can`t understand how to make my angular app authenticate to loopback, although i know access-token and userId
Have anybody any ideas how to do it?
Here is a valid code.
app.get('/auth/login', function(req, res, next) {
//workaround for loopback-password
//without this angular-loopback would make incorrect authorization header
res.cookie('access-token', req.signedCookies['access-token']);
res.cookie('userId', req.user.id);
res.redirect('/#auth/login');
});
The problem is that loopback-passport signs cookie:
res.cookie('access-token', info.accessToken.id, { signed: true,
maxAge: info.accessToken.ttl });
In string it looks something like the following "s:.eBvo8bpo9Q9wnNrPjjlG%2FAcYqWkxEgNFqn%2FO54rdGwY"
But loopback-angular just copies the access-token to header.authorization, so we need to put there plain cookie.

$cookieStore.get() return undefined in angularjs

I'm writing a cookie from a server through the response and it's fine the problem is when I try to read the same cookie using angularJs $cookieStore.get() returns always 'undefined', I have debugged with the developer tools using chrome and the cookie is there,
console.log($cookieStore.get("r"));
the $cookieStore seems to be injected and running ok, I'm just wondering why angularJs can't read the cookie.
Edit:
I tried with $cookies service and I get undefined as well.
I send the cookie in the server side without any problem, I'm getting the cookie in chrome developer tools
I'm using Service Stack and the code is the following:
public override object Logout(IServiceBase service, ServiceStack.ServiceInterface.Auth.Auth request)
{
var resp = service.RequestContext.Get<IHttpResponse>();
resp.Cookies.AddCookie(new Cookie { Name = "r", Path = "/", Value = "from server", HttpOnly = false, Discard = false, Expires = DateTime.Now.AddHours(12) });
return base.Logout(service, request);
}
I think $cookieStore is only meant to be used by itself, was 'r' set somewhere else? The docs say it provides a key/value store backed by cookies, not direct access to cookies. When I set 'myValue' to 'jason' it stores %22jason%22 (fiddle). This means you can set values to javascript objects if you want and the cookieStore will serialize and deserialize them for you.
Try using $cookies instead where you can just set properties and the values aren't encoded (fiddle):
$scope.setValue = function() {
$cookieStore.put("myValue", $scope.value);
};
$scope.getValue = function() {
$scope.value = $cookieStore.get('myValue');
};
$scope.setCookieValue = function() {
$cookies.otherValue = $scope.value;
};
$scope.getCookieValue = function() {
$scope.value = $cookies.otherValue;
};
Yes #Pedro is right in .NET ,for example, when doing an authentication with HttpCookie by default the attribute HttpOnly is true and in javscript -> document.cookie cant find the cookie you just saved to the browser.
It worked for me by setting to false HttpOnly when saving the cookie.

Resources