My WebAPI is, just that, an API backend for my UI to consume. In fact, I will have perhaps 10's of WebAPI services that my UI will use.
I'm having difficulty understanding what I need to consider in terms of security.
My APIs are secured using Bearer tokens, and allow https only. I have CORS set up and they only allow the origin https://my-front.end This all works great.
However.. how can I protect against C/XSRF and replay attacks on a WebAPI? Do I even need to?
Setting up anti-CSRF is rather painless with an ASP.NET MVC project, but how can you do it on a WebAPI one, from what I understand it relies on sending information, generated on the server, to the client to send along in the body of the request and through another channel (e.g. cookie or header). I read that you can protect against replay attacks by using a nonce (e.g. timestamp and random number) -- some how -- but can't find any implementation examples.
Is there anything else I need to consider?
Edit: front end uses vue.js, but we have a very competent JS programmer so any front-end implementations won't be a problem. It's just a matter of finding out what needs to be done!
May also be worth noting, for sake of obviousness, WebAPIs and FrontEnd run on different servers, so these are all, effectively, cross origin calls.
OK so. First up, you will need to be sending the XSRF token in a header. To do this you need to go to your ConfigureServices method and setup the AntiForgery service to expect this header.
public void ConfigureServices(IServiceCollection services)
{
services.AddAntiforgery(x => x.HeaderName = "X-XSRF-TOKEN");
services.AddMvc();
}
Next you need to generate the token. Because the front end and API are different services, you will need to work out when you do this. You could do it when you login for example, or you could have a dedicated endpoint doing this but the end result is the same.
You can either return the token value in a header or cookie it's up to you. In my example I've used a Cookie which I'll explain later but you can use a header if you like.
public class HomeController : Controller
{
private readonly IAntiforgery _antiForgeryService;
public HomeController(IAntiforgery antiForgeryService)
{
_antiForgeryService = antiForgeryService;
}
public IActionResult GetToken()
{
var token = _antiForgeryService.GetTokens(HttpContext).RequestToken;
HttpContext.Response.Cookies.Append("XSRF-TOKEN", token, new CookieOptions { HttpOnly = false });
return new StatusCodeResult(StatusCodes.Status200OK);
}
}
The IAntiforgery service should already be able to be used (It's part of the "AddMVC" call you will be using already in .net core services).
OK, so now we have returned a cookie with a token value. Now we just need to be able to send it back.
Here is some jQuery doing the legwork
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/js-cookie/2.1.4/js.cookie.min.js"></script>
<script type="text/javascript">
var token = Cookies.get("XSRF-TOKEN");
$.ajax({
url: "/",
contentType: "application/json",
type: "POST",
headers: {
"X-XSRF-TOKEN": token
}
});
</script>
Now something to note is that AngularJS automatically does this. When $http is used, it goes and looks for a cookie named "XSRF-TOKEN", and it will then send it as a header automatically. Because you are using Vue you will basically be doing this anyway but a bit more of a manual process.
Also important to note, DO NOT set this up to expect the cookie back as the CSRF token. Otherwise you've defeated the entire purpose of CSRF anyway.
Then you can go ahead and decorate your actions/controllers with the AntiForgery attribute.
[AutoValidateAntiforgeryToken]
public class HomeController : Controller
{
In conclusion what it basically boils down to :
Setup .net Core AntiForgery to expect a header value to use for the CSRF token
Have an endpoint that will manually generate a token for you to use and return it as a cookie/header
Have your front end read this value and store it for subsequent requests
On subsequent requests send the token value as a header (Not a cookie)
Mostly taken from here : http://dotnetcoretutorials.com/2017/05/18/csrf-tokens-angularjsjquery-asp-net-core/
Related
Context
I have a friend, his name is Bob.
Bob have a server with an application running & accessible only in local. To access this application from the outside world, Bob installed & configured a reverse proxy with nginx and the module auth_basic.
Each request go through the authentication process by the reverse proxy. Two cases :
If a HTTP GET request contain valid HTTP header parameter Authorization: Basic base64credentials, then the reverse proxy access the local application and response accordingly. Each sub-request will not require a new authentication because the browser cache the credentials and send them in every request automatically until we close the browser.
If a HTTP GET request doesn't contain valid HTTP header parameter, the reverse proxy respond directly with the HTTP header WWW-Authenticate: Basic realm="User Visible Realm". Then the browser automatically show a dialog box to enter credentials.
Everything works fine until here. It's like expected from basic auth specification.
Problem
Bob doesn't like the default dialog box from the browser and want a nice html page with a form. He configurate the nginx server to have his own html form.
The problem is that the HTML submit process for a form, by default, doesn't send the Authorization header parameter. Bob need to use a XMLHttpRequest. He implement it and receive a good 200 HTTP response from the server when the credentials are good.
Unlike the default form behavior, with the XMLHttpRequest, the browser doesn't cache the credentials automatically in case of success. So each sub-request display again the login form :'(
Bob can't change the front code of the local application to send by himself the credentials in each request (as with a SPA). Indeed, he doesn't have access to this app. He just have access to nginx conf and his own html login form. So storage is useless here.
Questions
Is it possible for bob to make the browser cache the credentials after receive the XHR response ?
(The goal is to behave like the default behavior even when he use a XMLHttpRequest)
EDIT :
Further explanation
The local app is running on localhost. Bob didn't develop this app and he can't edit it. This app doesn't provide authentication and Bob used the basic_auth module of nginx as a reverse proxy to authenticate people.
It works good but use the default behavior of browsers which implement Basic Auth specification. This behavior display an ugly form and cache the credentials when success. If Bob provide his own form the behavior go, which is normal because the Basic Auth specification require specific header parameter (Authorization: Basic ...) that HTML form can't provide. Bob need to use XHR to provide this parameter.
The question is, how get back the good behavior of the browser with XHR ?
We can only use JS on login.html and not on the local app. Here is the workflow :
HTTP GET request to the server
Server doesn't find Authorization parameter OR credentials are wrong
Server respond login.html
User provide credentials by form. XHR is emitted with Authorization parameter.
Server find Authorization parameter AND credentials are valid
Server give back the local app entry file (for example index.html)
Browser read index.html and want request other files (img, css, js...)
These sub requests will fail because no credentials provide in these requests.
If ugly default form use, the credentials are cached automatically and it works.
I precise also that a solution would be to replace nginx basic auth reverse proxy by a real backend app and another authentication system (with cookie for example which are send automatically) which would work as a reverse proxy but it is not the question asked.
EDIT 2 :
Why Bob can't use storage solution ?
In the ulgy form scenario, he doesn't have HTML login file. When the browser client ask a request to the server, the server only response a HTTP response with the WWW-Authenticate header but without HTML content. The simple fact to have this header parameter display a form. Just putting the good credentials will send back a 200 HTTP Response and the browser will cache the credentials and send it in every request with the HTTP header Authorization: Basic.
In the login.html scenario, after a success login, we need to send back in every request the HTTP header Authorization: Basic (not a cookie, because it's how work Basic Auth spec and Bob doesn't have any backend, just the nginx module). It's possible to send this header from the login.html because we can attach JS on it. But then, the next pages respond by the server will be HTML files from the local app, where Bob doesn't have access to their HTML and can't attach JS on them to provide header Authorization: Basic for the next requests. A cookie could be stored from the login.html file, but this cookie need to be retrieved from the other pages and used to send header Authorization: Basic, which is impossible because Bob doesn't have access to the JS of these pages.
Thank you in advance.
Since you're already using ajax, just have javascript set and read a cookie:
(I use jQuery here, for simplicity, replace the ajax call with the appropriate syntax if you're not using jQuery):
function getCookie(cookiename) {
/* a function to find a cookie based on its name */
var r = document.cookie.match('\\b' + cookiename + "=([^;]*)\\b");
// document.cookie returns all cookies for this url
return r ? r[1] : undefined;
// return the regex capture if it has content, otherwise return undefined
}
function getData(auth_basic) {
$.ajax({
url: 'url_of_nginx...',
headers: {
'Authorization': 'Basic ' + auth_basic
} // send auth header on xmlhttprequest GET
}).next(function ajaxSuccess(data) {
// data from the nginx
document.cookie = '_auth_cookie=' + auth_basic;
// store my auth in a cookie
}, function ajaxFailed(jqXHR) {
// do something on failure, like
showLoginForm()
});
}
function showLoginForm() {
/* function to render your form */
// attach an event handler to form submission
$('#submit_button_id').click(function form_submitted(login_evt) {
// I clicked login
login_evt.preventDefault(); // don't really submit the form
// get my field form values
username = $('#username_input_field').val();
password = $('#password_input_field').val();
// I base64 the auth string
var auth_basic = btoa(username + ':' + password);
// try to auth
getData(auth_basic);
});
}
var auth_cookie = getCookie('_auth_cookie');
if (auth_cookie === undefined) {
// I have no cookie
showLoginForm()
} else {
getData(auth_cookie)
}
I am working on Java application . Front end would be Angular2 .
If I try to open my application home page( index.html is configured in web.xml as default page ) . Access URL should be http://localhost:8080/MyWebApp .
Then I have taken into an standard organization's login page for authentication. If authentication succes , HTTP Authorization token will be set in the request header and finally control comes to display my application home page.
If I use jsp, I can get request header as,
String authHeader = request.getHeader("authorization");
out.println("<h2>HTTP Authorization header:</h2>");
if (authHeader == null) {
out.print("No authorization header");
} else {
out.print("<textarea readonly id='authHeader' rows=\"5\" cols=\"80\">" + authHeader + "</textarea>");
}
But we are using html as front end, because of angular 2 .
So for my scenario, how I can I get the request header and token.
Please don't hesitate to edit my question, if it is not clear.
You can't get a value of a header from client-side JavaScript. The only exceptions are the User-Agent and Referrer headers, because the browser provides the values in the document and navigator objects.
You said you are working on a Java application with an Angular 2 front end and some other application provides a token (might be useful to specify if this is something standard, e.g. OAuth2). I will assume that it is a custom token. I believe you also meant you have some server side component, a servlet.
What you can do is to implement the authentication using the servlets (or even JSPs) and then redirect back to the Angular 2 front end application, passing the token in the URL as a query parameter. URL is easy to read in Angular 2. However this is not very secure, even if you use something like JWT. As an alternative to URL, you can use the Set-Cookie header and then read the cookie from Angular.
What would be almost secure would be to authenticate the user using the server side (servlet or even JSP). Then create a one-time token which is passed in the URL as a query parameter when redirecting to your HTML page. Then use the one-time token in a call to the server again to retrieve the real authentication token using a proper REST call from Angular 2 with request and response.
Depends on how much control you have and what kind of authentication the auth application uses, you might want to take a look at the OAuth2. It deals with plenty of different authentication scenarios. Specifically the OAuth2 implicit grant flow is used to authenticate users from client-side only applications. Even if you can't use that, it will give you some ideas.
When you are using a server-side authorization, your server put headers with authorization to your HTML pages. But also you can put this tokens to your page response by meta tags at server side. And then access to meta tags by js.
<meta name="_csrf" content="${_csrf.token}"/>
<meta name="_csrf_header" content="${_csrf.headerName}"/>
Meta tags are similar to response headers and can complete or override response headers.
Read this post please Spring Security CSRF Token not working with AJAX call & form submit in same JSP
You can handle this at server side(JSP's expressions work on server side), create a handler method on server where you can check header and then redirect to your Angular App.
I think we can use HTTP HEAD method as JQUERY AJAX request in your HTML page .
https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol
The HEAD method asks for a response identical to that of a GET request, but without the response body. This is useful for retrieving meta-information written in response headers, without having to transport the entire content.
ajaxRequest = $.ajax({
type: "HEAD",
async: true,
url: 'index.jsp',
success: function(message){
var headerValue =ajaxRequest.getResponseHeader('Content-Length')]);
}
});
There are various way to solve this issue as I faced it a lot before and what I prefer is;
When authentication is completed in login page and a token generates I store it into HTML storage, make be in localStorage.
The main point you should understand that your views should not be accessed directly and there has to be a authentication (or may be a authorisation) step(s) before accessing the page(view).
So what you can do is set a URI for accessing any page, consider that is;
http://host/appname/pageName
And when you connect with this URI via ajax call add the token that is stored in localStorage in headers. And check all authentication and authorisation works and if success return the view(as the pageName suggested in the URI), else return the login view.
If i understand you correctly,
Angularjs is a client side framework and is intended to run inside a browser without any intervention of server by reducing its load by serving the application logic
All operations that need to be performed by angular will only be initiated at client side by the browser after loading the HTML and javascript.
The scope of angular is only limited to that area any way it is not a disadvantage it is the actual intention of client side frameworks.
Regarding request response headers you can only have access to headers of AJAX request
Following are the solutions to these problems:-
If you are using tomcat or any servelet container in order to serve the application or hosting angular code you can use JSP insted of HTML,since JSP is processed to html by the servelet container before passing it to client side.I think this solution will work in your case based on my inference form your question
Otherwise configure servelet that process the success and failure handlers from the authentication server and from angular you need to poll the servelet for getting the request header value.
I have created an API that is supposed to answer to both mobile devices and web browsers. For example, /web/toys for web and /API/toys for JSON responses. I am using JW Tokens as a means of authentication.
I am displaying forms in HTML and in the background, I call jQuery Ajax's methods to POST to my APIs. I am keeping the access_tokens in the session cookie. To prevent CSRF attacks, I am using Flask-JWT-Extended.
When I decorate a view with #jwt_required and CSRF is set to True, I get missing JWT in headers and cookies, even when the cookies were being set and transferred. I checked the source code and found that it is important to set X-CSRF-TOKEN in the request header. However, since the endpoint answers to both GET and POST calls, how can I set the headers in the GET call without resorting to loading the complete page using jQuery.? Basically, I want to show the form on the webpage, and when the user clicks submit, the form be transferred using jQuery to the existing API. If there is a better way to handle things, I would love to know it.
Thanks!
Author of Flask-JWT-Extended here. As you have discovered, with this extension we are currently doing CSRF protection for every type of request. However, CSRF protection is only really needed on state changing requests: See: https://security.stackexchange.com/questions/115794/should-i-use-csrf-protection-for-get-requests/115800
The benefit of protecting all requests types is that if you have an endpoint that incorrectly changes state in a GET request (there is no technical reason this couldn't happen), it becomes vulnerable to CSRF attacks. Now if the backend is designed more 'up to spec', this is no longer a problem. It sounds like I need to update Flask-JWT-Extended to allow for ignoring CSRF protection on certain types of requests, just like how Flask-WTF operates. I'll try to get this updated today.
Alternately, if your backend is serving JSON instead of html directly (such as a REST api backend and javascript frontend), you can use Ajax to do GET requests with CSRF tokens. In this use case, we could use an Ajax call along these lines.
get (options) {
let self = this
$.ajax({
method: 'GET',
dataType: 'json',
headers: {
'X-CSRF-TOKEN': Cookies.get('csrf_access_token')
},
url: "some_url",
success (result, statusText) {
// Handle success
},
error (jqXHR, textStatus, errorThrown) {
//handle error
}
})
}
EDIT: I also want to preserve the CSRF error messages if the CSRF token isn't present and you are using both headers and cookies for JWTs. Progress on both of these can be tracked here:
https://github.com/vimalloc/flask-jwt-extended/issues/28
https://github.com/vimalloc/flask-jwt-extended/issues/29
I am working on a simple website using jwt. (node.js, koa.js)
Most example codes including expressjs, I cannot find the client-side example
about how to deal with jwt sent from a server.
Only one example (https://github.com/auth0-blog/cookie-jwt-auth) showed me that
[index.html]
... script src="app.js...
[app.js]
$.ajax({
type: 'POST',
url: 'http://localhost:3001/secured/authorize-cookie',
data: {
token: token
},
headers: {
'Authorization' : 'Bearer ' + token
}
After I read this example, I felt that I should have some scripts for users to send an authorization header with jwt. Is it right?
Or are there some front-end frameworks that deal with authorization header?
Thank you for reading newbie'q question.
Yes, you will need to define a mechanism for sending the user's JWT back to the server. It's up to you to decide where the JWT will live in the request -- the most common places are in the Authorization header, or by setting a cookie on the browser (which will be sent along with every HTTP request). You should also consider whether you want the JWT to persist across sessions / page reloads (using for example document.cookie or localStorage).
If you choose not to use the cookie approach, you can configure all $.ajax requests to set your Authorization header "pre-flight" using $.ajaxSetup({...}) (but this is a bit of a sledge-hammer approach). Manually setting the Authorization header on each individual $.ajax request, as you've demonstrated above, is a good option too.
If you want to skip headers all together, you can send the JWT inside the body of your request (as JSON, for example).
I have an Angular application that stores JWT tokens in localstorage to provide authentication. What I want to do is figure out how to grab this JWT token and insert it into an HTTP GET request, that renders as a completely new web page (NOT a returned object from an XMLHTTP request that displays in the same page...).
Is this possible? The only way I've found to do something similar would be to use basic HTTP authorization, such as:
username:password#www.hypotheticalwebsite.com
And I'm assuming I could pass in my entire JWT there.
I'm using Express.js to handle routing on my Node.js backend.
Simply readout the JWT from your locale storage and then insert the JWT to the Authorization Header or you could add it as a GET parameter.
I would recommend the header.
On the next side simple read the JWT out.
If the processing is needed on all pages of the express instance, do it in the express-middleware.
Update
If you want to use a normal link you must use a parameter like ?auth_token=12345.
To add it was a header you can use $http and use a method in your controller.
For example:
Controller
$scope.openLink = function() {
var config = {
headers: {
'Authorization': '12345',
}
};
$http.get("<your url>", config);
}
View
Test
Shielding Admin Interface
To be secure you should add roles in your api and only allow the administration relevant calls for administrators and also check on angular side if the user is allowed to open the UI with the role the user has. For angular there are a few acl modules like: github.com/mikemclin/angular-acl