Is it possible to use basic auth with the following snippet to make a simple post request to a site running locally?
app.controller('post', function($scope, $http) {
$http.post("http://localhost:8888/angular//wp-json/posts", {'title':'Posting from Angular'})
.success(function(data, status, headers, config) {
// this callback will be called asynchronously
// when the response is available
document.getElementById("response").innerHTML = "It worked!";
}).
error(function(data, status, headers, config) {
// called asynchronously if an error occurs
// or server returns response with an error status.
document.getElementById("response").innerHTML= "It did not work!";
alert(status)
});
});
When you say basic auth, do you mean HTTP Basic Authentication?
If that's what you mean: then NO. HTTP Basic Authentication requires that your client (in this case, your web browser) needs to know your API keys and be able to send them over the network to your server. Since you're running Angular inside of a web browser, this means that any user visiting your site would theoretically be able to view your API credentials in plain text by inspecting the browser's Javascript console.
This is a HUGE security risk.
What you want to do instead is use a protocol like OAuth2 with the Password Grant (more info here: https://stormpath.com/blog/token-auth-spa/).
OAuth2 allows you to basically do this:
Have a user log into your application with a username/password.
When the login is successful, a token will be returned and stored in a web browser cookie that is HIDDEN FROM JAVASCRIPT.
Then you can use Angular to POST to your API service, as the browser will automatically attach the relevant token API credentials in the HTTP Authorization header automatically.
Your server can then grab the token out of the HTTP Authorization header, and validate the credentials in this way.
This is the only safe way to access an authenticated API service from a browser.
Related
I have an MVC site with an embedded angular client and I've recently implemented an anti forgery XSRF token as a security measure.
I have set it up in Startup.cs as follows:
services.AddAntiforgery(options => options.HeaderName = "X-XSRF-TOKEN");
app.Use(next => context =>
{
if (string.Equals(context.Request.Path.Value, "/", StringComparison.OrdinalIgnoreCase) ||
string.Equals(context.Request.Path.Value, "/index.html", StringComparison.OrdinalIgnoreCase))
{
// We can send the request token as a JavaScript-readable cookie, and Angular will use it by default.
var tokens = antiforgery.GetAndStoreTokens(context);
context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken,
new CookieOptions() { HttpOnly = false });
}
return next(context);
});
And I've implemented it within my angular front-end like so:
{ provide: XSRFStrategy, useFactory: xsrfFactory}
export function xsrfFactory(): CookieXSRFStrategy {
return new CookieXSRFStrategy('XSRF-TOKEN', 'X-XSRF-TOKEN');
}
And protecting my controllers like:
[Authorize] //Validation of AzureAD Bearer Token.
[ValidateAntiForgeryToken]
public class UserController : Controller
It is intended that the X-XSRF-TOKEN header be validated with any call to my API, and this works successfully for all calls in the original session. However, my app uses Adal to log the user in, and after the redirect from a successful login, this validation step fails and I receive a 400 from my API for any subsequent calls.
The original X-XSRF-TOKEN header is still sent with all outgoing requests from my angular client after the login so I suspect it must be that my server side no longer has the token to validate against, or my server has generated a new one and my client doesn't retrieve it. But for whatever reason it breaks down and it's very hard to debug without creating some custom filter so I can see what's going on inside it.
Is there a way to reset this token after a client side redirect so that both my server and client share common knowledge of it again? Or should I be generating the token in my Index.html for example?
EDIT
Edited controller decoration above for missing [Authorize] attribute.
So my controller is protected by a step validating the AzureAD Bearer token as well as the Anti-Forgery validation. Removing the AzureAD Validation as a test did not resolve the issue, oddly.
Error on failing API calls displays in output after Adal login as:
The provided anti-forgery token was meant for a different claims-based user than the current user.
Based on my understanding, you were protecting the controller using token. For this issue is expected, you can refer the progress of validate the anti-XSRF tokens from below(refer this link):
To validate the incoming anti-XSRF tokens, the developer includes a ValidateAntiForgeryToken attribute on her MVC action or controller, or she calls #AntiForgery.Validate() from her Razor page. The runtime will perform the following steps:
The incoming session token and field token are read and the anti-XSRF token extracted from each. The anti-XSRF tokens must be identical per step (2) in the generation routine.
If the current user is authenticated, her username is compared with the username stored in the field token. The usernames must match.
If an IAntiForgeryAdditionalDataProvider is configured, the runtime calls its ValidateAdditionalData method. The method must return the Boolean value true.
Since you were developing the SPA application with back-end web API, when the request to the web API, it will always issue the anti-XSRF token with no identity. And when you send the request to the back-end with anti-XSRF and Azure AD token, this time the web API already authenticate the request via the Azure AD token. And it will always return false when checking anti-XSRF token to match the identity information.
In this scenario, if the back-end only using the bear token authentication and store the token with session storage, there is no need to enable XSRF prevention since others is not able to steal the token and forge the request.
If your back-end also support the cookie authentication or basic auth, NTLM etc, you can disable the identity checking by adding the following to your Application_Start method: AntiForgeryConfig.SuppressIdentityHeuristicChecks = true.(refer this link)
More detail about XSRF/CSRF abouth oauth and web API, you can refer the threads below:
How does ValidateAntiForgeryToken fit with Web APIs that can be accessed via web or native app?
AntiForgeryToken does not work well with OAuth via WebAPI
Try replacing [ValidateAntiForgeryToken] with [AutoValidateAntiforgeryToken]
https://github.com/aspnet/Antiforgery/blob/dev/src/Microsoft.AspNetCore.Antiforgery/Internal/DefaultAntiforgeryTokenGenerator.cs
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.
I am sing OAuth2 in WebAPI project. I am authenticating user request in OWIN middleware. On successfull authentication I am sending an JWT access token to client. Now I can validate subsequent request at server and use [Authorize(Roles="myRole")] attribute on Api Controllers.
But how can I show validate client content in AngularJs and show pages based on user role? I have JWT at client and no idea how to get user role out of it?
Is it a good approach to extract information from JWT?
You will need to parse that JWT and get the values out. You can do that with the help of the angular-jtw library.
1) Download the angular-jwt.min.js (https://github.com/auth0/angular-jwt)
2) put a dependecy of "angular-jwt" on the application module:
var app = angular.module("YOUR_APP", ["angular-jwt"]);
3) pass the jwtHelper to your service or controller or wherever it is that you wish to use it.
app.module.factory("YOUR_SERVICE", function(jwtHelper){
...
});
4) use the decodeToken method of the jwtHelper you passed in to decode your token
For example, the code below is parsing out the role object from a jwt that came back from my service endpoint. Upon succssful return from the server the role is extracted from the jwt and returned.
return $http.post(serviceEndPoints.tokenUrl, data, config)
.then(function (response) {
var tokenPayLoad = jwtHelper.decodeToken(response.data.access_token);
//Now do whatever you wish with the value. Below I am passing it to a function: (determineRole)
var userRole = determineRoles(tokenPayLoad.role);
return userRole;
});
};
Hope that helps
//Houdini
Currently we don't offer anything that would help you to take advantage of that information on the client. Also note: as today we do not validate the token on the client, we cannot really trust its content... while the [Authorize] attribute on the server side gets the role info only after the pipeline before it had a chance of validating the signature and deciding that the token is valid.
We might introduce something that will help with this scenario in the future, but for the time being you'd need to write custom code or rely on the server side to echo things back.
My backend is setup on AWS and is running an expess webserver. It is configured with passportjs.
On the frontend I want all users to login through linkedin. When they press the button it issues a http GET command using angular's $http.get service. The basic workflow I am trying to achieve is the following:
1) Client opens the app and hits sign in with linkedin and the get request is issued to the backend express server
2) The express server uses passportjs for linkedin oauth2 authentication which subsequently forwards the request to the linkedin server
3) Linkedin's server then responds to the client with a login page in response to the clients request
4) Once the page in step 3 is successfully verified with the users login credentials, the access token is sent to the express server since I configured the linkedin redirect URL to it.
5) The express server then receives the user profile details and then sends across some data to the client.
Since I am using the ionic Framework I would like to receive the data and then load up a page defined in the ionic templates folder(local to the frontend) and populate it with data received from the server at step 5.
In order to do this I am trying to issue an $http.get request which fails with the error:
404 on Step 2.
$http.get('http://www.example.com:3000/auth/linkedin/').
success(function(data, status, headers, config) {
console.log('success');
}).
error(function(data, status, headers, config) {
console.log('failure '+data+', ' +status +', '+headers+', '+config)
});
This prints out:
"failure , 404, function (name) {
"use strict";
if (!headersObj) headersObj = parseHeaders(headers);
if (name) {
return headersObj[lowercase(name)] || null;
}
return headersObj;
}, [object Object]"
I am not sure if this is the way to go about it.
The other method that gave more success was using window.location('http://www.example.com:3000/auth/linkedin/'). This was able to follow up the redirect to the linkedin login page and upon successful login, the profile was returned to the express server. This method worked fine upto step 4. The problem was at step 5, I am not able redirect the user back to the other templates defined in the app.
Instead of firing GET requests all over the place, You should write an angular service to manage authentication.
Such a service should expose functions like login(), is_autheticated(), get_username() and etc'.
Then, inject the LoginService into your controllers and you won't have a hard time with requests and promises.
As for your redirect problem, take a look at ui-router.
I'm stacking on a shit problem and can't find a solution... maybe has someone an idea.
I have a rest api based on sails.js and send a post request via Angular.js - works fine I get all response data!
But im unable to read the header cookie. The cookie is always null...
In the header should be a 'Set-Cookie'. If I call the same function with a Rest tool I get the cookie header.
I'm working local host port :8000. The rest api is on port :1337
$http.post(url, json)
.success(function(data, status, headers, config) {
console.log(headers('Server'));
console.log(headers('Set-Cookie'));
})
.error(function(data, status, headers, config) {
console.log("error");
console.log("status " +status);
console.log("status " +angular.toJson(data));
});
Any idea?
Cheers!!
First of all, you should never need to get the set-cookie header in Javascript; the browser handles setting the cookie for you. If you want to check if the cookie was part of the response, it's better to use your browser's development console.
More to the point, the issue here is that the cookie is not going to be set at all since this is a cross-origin AJAX request (different ports counts as a different origin) and you probably haven't set the CORS settings in Sails to allow the request through.
The full documentation for CORS in Sails is here. The easiest way to get started with testing it is to open your /config/cors.js file and set allRoutes to true; this will allow all CORS requests to go through unimpeded, regardless of their origin or the route they're trying to access. Once you've got that working, you can use the more fine-grained settings to lock down access if you want.
Note that by default, only a very few response headers from a cross-origin request will be made available via Javascript, of which set-cookie is definitely not one!