How do I implement secure OAuth2 consumption in AngularJs? - angularjs

I'm setting up oauth2 with Salesforce connect using AngularJS. When I attempt the initial GET using $http I get CORS errors - Access-Control-Allow-Origin not allowed for my client. However, using the hack below in my controller function works.
Is there a better way to do this in AngularJs given that I don't have control over the server? My backend is Firebase so it would be great if I could do this through FIrebase like I can for Facebook :
$scope.auth = function () {
var authUrl = $scope.AUTHORIZATION_ENDPOINT +
"?response_type=token" +
"&client_id=" + $scope.CLIENT_ID +
"&redirect_uri=" + "https://www.xyz/";
window.location = authUrl ;

The cors issue happens when make asynchronous call to different server via browser.
So it doesn't appear if the call is made from another server. so you have to use a proxy server which in turn makes a call to your actual server.
You can try the below proxy server which is straightforward.
https://www.npmjs.com/package/cors-anywhere
or you can write your own proxy server which runs on the same domain as your client app and proxies your request to the secured server.

Related

AngularJS - Request to localhost server fail

I have made an api end point using express.
Now im trying to get the data from my localhost server that is running on port 3010.
$scope.getBooks = function(){
$http.get('/api/books').then(function(resp){
$scope.books = resp;
});
}
The function is working good because i can test whit the JSONPlaceholder.com.
I cannot get my data, im using gulp on port 3002 and my server is in port 3010, he is running and working good, i can see my data using postman.
why can i get my data from the localhost server ?
Thank You
This issue happens because the API endpoint you are accessing does not implement CORS. If you run your code in Chrome and look at the console, you will see an error:
XMLHttpRequest cannot load .....
The solution is to change the API endpoint to set the Access-Control-Allow-Origin header to either the wildcard * or to the domain where the page using the JavaScript code will be served from.
For More Details on CORS
To prevent websites from tampering with each other, web browsers implement a security measure known as the same-origin policy. The same-origin policy lets resources (such as JavaScript) interact with resources from the same domain, but not with resources from a different domain. This provides security for the user by preventing abuse, such as running a script that reads the password field on a secure website.
If your site is on port 3002 and your server is on port 3010 then you must specify the URL of the entire server's location
$scope.getBooks = function(){
$http.get('http://localhost:3010/api/books').then(function(resp){
$scope.books = resp;
});
}
Specify the entire url in AngularJS code and use CORS middleware in the express backend.
https://github.com/expressjs/cors
This is bad way of doing front end(AngularJs) and backend development parallely on local on same machine. Try to fit Front-end repository to be hosted by BE server and use it in following way :
If you are doing FE development locally and backend server is also hosted locally.
use following line as common baseUrl
var baseUrl = "//" + window.location.host +'/'
Doing so, you don't need to update while committing changes to prod environment.
$scope.getBooks = function(){
var _url = baseUrl + '/api/books'
$http.get(_url).then(function(resp){
$scope.books = resp;
});
}
Above code works in case of same server FE and BE.
If you have different servers locally , you need to work more with setting :
You can use express-http-proxy
var proxy = require('express-http-proxy');
app.use('/api/', proxy('http://localhost:3010'));
In higher of version of angular 4+ , we have such setting as in initial configuration.
READ ARTICLE :
https://juristr.com/blog/2016/11/configure-proxy-api-angular-cli/
You cannot just write /api/books. You need to specify your port number of API running on localhost.
In your case localhost:3010/api/books

Endpoints work on localhost but not on app engine (omitted https://)

I would like to access my endpoint function without using OAuth.
I follow the guide on Simple access to API and tweak the code a little.
I can access the API on localhost - I have to wait for about five minutes for them to load. Then they appear in the explorer /_ah/api/explorer.
But I can't access the endpoint functions on app engine:
the functions load but I can't access them:
var rootpath = "//" + window.location.host + "/_ah/api";
gapi.client.load('helloworldendpoints', 'v1', makeRequest, rootpath);
// callback gets executed
...
var request = gapi.client.helloworldendpoints.sayHello();
//any code below this does not get executed
So this has been the most excruciating error for me in coding so far!:D
instead of
https://helloworld-146410.appspot.com/
https://1-dot-helloworld-146410.appspot.com/
I didn't include the https:// and used
helloworld-146410.appspot.com
1-dot-helloworld-146410.appspot.com
without https: I wasn't able to access the endpoints
Can I get the 30+ hours of my life back please?:D How could have I avoided this mistake or figured it out faster?

How to allow an angularjs app authenticate in rails api?

I have two separated apps (AngularJS + Express + NodeJS) and a Ruby on Rails API (rails-api).
To keep it simple my client application will not authenticate users but it will need to consume the data from api.
I've used authenticate_with_http_token method in the rails-api and it works (if you pass in header or query params) but I can't retrieve the token as a security form (from environment variable I think) inside the angularjs app. Yes I can access the env vars from express server, but I can't share this values to be used at angular.
App.run(function($http ...) {
// $http.defaults.headers.common.Authorization = proccess.env.AUTH_TOKEN_API <- I can't do this. But is something like this I need.
$http.defaults.headers.common.Authorization = 'Token token=78db1w26vve20aa36...';
How to keep security in api in this case?

AngularJS: Sending Authentication Headers over Websocket

I am having difficulty authenticating requests to a WebSocket API.
The Site I am working with (www.bitmex.com) provides a REST API and a WebSocket API.
Both of their API's allow authentication with an API Key.
Authentication Requirements
The API provides the following documentation for authentication with API Keys:
Authentication is done by sending the following HTTP headers:
api-expires: a UNIX timestamp in the future (eg: 5 seconds).
api-key: Your public API key. This the id param returned when you create an API Key via the API.
api-signature: A signature of the request you are making. It is calculated as hex(HMAC_SHA256(verb + url + nonce + data)).
REST API
I've created a NodeJS module for sending requests to the REST API, I've defined the following headers
headers = {
"User-Agent": "BitMEX NodeJS API Client",
"api-expires": expires,
"api-key": this.api_key,
"api-signature": this.signMessage(verb, reqUrl, expires, params)
};
where the signMessage function looks like:
BitMEX.prototype.signMessage = function signMessage(verb, url, nonce, data) {
if (!data || _.isEmpty(data)) data = '';
else if(_.isObject(data)) data = formatParameters(data);
return crypto.createHmac('sha256', this.secret).update(verb + url + nonce + data).digest('hex');
};
This works great for the REST API and does everything I need it to in the backend of my application.
WebSocket API
I am trying to use WebSocket get realtime data and display it in a browser based interface.
The documentation on the site states:
To use an API Key with websockets, you must sign the initial upgrade request in the same manner you would sign other REST calls.
I've been implementing this in AngularJS using the ng-websocket module.
exchange.dataStream = $websocket('wss://testnet.bitmex.com/realtime');
exchange.dataStream.onMessage(function incoming (message) {
console.log("BitMEX: WS MESSAGE RECEIVED: " + message.data);
// .. handle data here ...
});
exchange.dataStream.send({"op":"getAccount"});
The problem that I've run into is I can't find anyway to send the headers using ng-websocket that are needed for authentication.
If I am presently logged in to BitMEX from another tab in my browser, this will connect, get the data, and work as expected.
However, if I am not currently logged in to the site, it will throw the following error:
BitMEX: WS MESSAGE RECEIVED: {"status":401,"error":"Not authenticated.","meta":{},"request":{"op":"getAccount"}}
There is a python example provided here: https://github.com/BitMEX/market-maker/blob/master/test/websocket-apikey-auth-test.py that goes through the Authentication process,
but I haven't found a way to accomplish this in AngularJS.
Summary
#1) When logged in to BitMEX, and the Websocket is working, is Chrome somehow using the website's cookies to authenticate the websocket requests?
Looking at an overview of websockets here: http://enterprisewebbook.com/ch8_websockets.html
The initial handshake upgrades the connection from "HTTP" to the WebSocket protocol,
#2) Because this initial connection is over HTTP, is there any way to attach the headers required to this initial HTTP request?
If you read the Python example, the first thing it sends is {"op": "authKey", "args": [API_KEY, nonce, signature]} then it sends {"op": "getAccount"}
Python example line #44 and line #51
How Chrome does it is another question.

How can I add a spring security JSESSIONID with SockJS and STOMP when doing a cross-domain request?

I am having the following problem. I will describe 3 use cases - two which work and the other one which doesn't.
I have an AngularJS client using SockJS with STOMP. In the backend I have a Spring application. Client is in domain domainA.com, backend is in domainB.com.
var socket = new SockJS(("http://domainB.com/myApp/messages"));
stompClient = Stomp.over(socket);
stompClient.connect('guest', 'guest', function(frame) {
...
}
In the backend there are Cors filters and the Cross-origin calls are possible. All works fine.
Use Case 1. Client domainA, Server domainB
My application is unsecured on the backend side. I subscribe like below:
stompClient.subscribe('/topic/listen', function(message) {
showMessage(JSON.parse(message.body).content);
});
All works fine.
Use Case 2. Client domainB, Server domainB
My application is secured on the backend side with Spring security. Authentication is done through a form - username and password. Nothing uncommon.
In this case the client is on domainB.com, same as the backend. All works fine, the difference is that I use a different subscription method:
stompClient.subscribe('/user/queue/listen', function(message) {
showMessage(JSON.parse(message.body).content);
});
in order to benefit from getting the principal from the security session. All works well. No issues.
The JSESSIONID cookie is added to the connection request in new SockJS(("http://domainB.com/myApp/messages"));.
Use Case 3. Client domainA, Server domainB
The application is secured the same as in UC2. However, the client is now on a different domain.
The JSESSIONID is NOT added to the connection request. The connection to websocket in Spring is unauthenticated and redirected back to login page. This repeats and makes it impossible to connect.
Why is the JSESSIONID cookie not populated with the websocket requests in this case?
Cheers
Adam
As part of SockJS protocol, a http GET is sent to websocket server for negotiating the supported protocols. It's done using XmlHttpRequest which won't add any cookies stored for a different domain than its own domain the web application and scripts are served due to same-origin policy implemented in every modern web browser.
You should resort to a way of circumventing the same-origin policy.
I think you'll find the answers you are looking for here : http://spring.io/blog/2014/09/16/preview-spring-security-websocket-support-sessions
the trick to implement a HandshakeInterceptor

Resources