I'm implementing a website in Angular.js, which is hitting an ASP.NET WebAPI backend.
Angular.js has some in-built features to help with anti-csrf protection. On each http request, it will look for a cookie called "XSRF-TOKEN" and submit it as a header called "X-XSRF-TOKEN" .
This relies on the webserver being able to set the XSRF-TOKEN cookie after authenticating the user, and then checking the X-XSRF-TOKEN header for incoming requests.
The Angular documentation states:
To take advantage of this, your server needs to set a token in a JavaScript readable session cookie called XSRF-TOKEN on first HTTP GET request. On subsequent non-GET requests the server can verify that the cookie matches X-XSRF-TOKEN HTTP header, and therefore be sure that only JavaScript running on your domain could have read the token. The token must be unique for each user and must be verifiable by the server (to prevent the JavaScript making up its own tokens). We recommend that the token is a digest of your site's authentication cookie with salt for added security.
I couldn't find any good examples of this for ASP.NET WebAPI, so I've rolled my own with help from various sources. My question is - can anyone see anything wrong with the code?
First I defined a simple helper class:
public class CsrfTokenHelper
{
const string ConstantSalt = "<ARandomString>";
public string GenerateCsrfTokenFromAuthToken(string authToken)
{
return GenerateCookieFriendlyHash(authToken);
}
public bool DoesCsrfTokenMatchAuthToken(string csrfToken, string authToken)
{
return csrfToken == GenerateCookieFriendlyHash(authToken);
}
private static string GenerateCookieFriendlyHash(string authToken)
{
using (var sha = SHA256.Create())
{
var computedHash = sha.ComputeHash(Encoding.Unicode.GetBytes(authToken + ConstantSalt));
var cookieFriendlyHash = HttpServerUtility.UrlTokenEncode(computedHash);
return cookieFriendlyHash;
}
}
}
Then I have the following method in my authorisation controller, and I call it after I call FormsAuthentication.SetAuthCookie():
// http://www.asp.net/web-api/overview/security/preventing-cross-site-request-forgery-(csrf)-attacks
// http://docs.angularjs.org/api/ng.$http
private void SetCsrfCookie()
{
var authCookie = HttpContext.Current.Response.Cookies.Get(".ASPXAUTH");
Debug.Assert(authCookie != null, "authCookie != null");
var csrfToken = new CsrfTokenHelper().GenerateCsrfTokenFromAuthToken(authCookie.Value);
var csrfCookie = new HttpCookie("XSRF-TOKEN", csrfToken) {HttpOnly = false};
HttpContext.Current.Response.Cookies.Add(csrfCookie);
}
Then I have a custom attribute which I can add to controllers to make them check the csrf header:
public class CheckCsrfHeaderAttribute : AuthorizeAttribute
{
// http://stackoverflow.com/questions/11725988/problems-implementing-validatingantiforgerytoken-attribute-for-web-api-with-mvc
protected override bool IsAuthorized(HttpActionContext context)
{
// get auth token from cookie
var authCookie = HttpContext.Current.Request.Cookies[".ASPXAUTH"];
if (authCookie == null) return false;
var authToken = authCookie.Value;
// get csrf token from header
var csrfToken = context.Request.Headers.GetValues("X-XSRF-TOKEN").FirstOrDefault();
if (String.IsNullOrEmpty(csrfToken)) return false;
// Verify that csrf token was generated from auth token
// Since the csrf token should have gone out as a cookie, only our site should have been able to get it (via javascript) and return it in a header.
// This proves that our site made the request.
return new CsrfTokenHelper().DoesCsrfTokenMatchAuthToken(csrfToken, authToken);
}
}
Lastly, I clear the Csrf token when the user logs out:
HttpContext.Current.Response.Cookies.Remove("XSRF-TOKEN");
Can anyone spot any obvious (or not-so-obvious) problems with that approach?
Your code seems to be fine. The only thing is, you don't need most of the code you have as web.api runs "on top" of asp.net mvc, and latter has built in support for anti-forgery tokens.
In comments dbrunning and ccorrin express concerns that you only able to use build in AntiForgery tokens only when you are using MVC html helpers. It is not true. Helpers can just expose session based pair of tokens that you can validate against each other. See below for details.
UPDATE:
There is two methods you can use from AntiForgery:
AntiForgery.GetTokens uses two out parameters to return cookie token and form token
AntiForgery.Validate(cookieToken, formToken) validates if pair of tokens is valid
You totally can repurpose those two methods and use formToken as headerToken and cookieToken as actual cookieToken. Then just call validate on both within attribute.
Another solution is to use JWT (check eg MembershipReboot implementation)
This link shows how to use built in anti-forgery tokens with ajax:
<script>
#functions{
public string TokenHeaderValue()
{
string cookieToken, formToken;
AntiForgery.GetTokens(null, out cookieToken, out formToken);
return cookieToken + ":" + formToken;
}
}
$.ajax("api/values", {
type: "post",
contentType: "application/json",
data: { }, // JSON data goes here
dataType: "json",
headers: {
'RequestVerificationToken': '#TokenHeaderValue()'
}
});
</script>
void ValidateRequestHeader(HttpRequestMessage request)
{
string cookieToken = "";
string formToken = "";
IEnumerable<string> tokenHeaders;
if (request.Headers.TryGetValues("RequestVerificationToken", out tokenHeaders))
{
string[] tokens = tokenHeaders.First().Split(':');
if (tokens.Length == 2)
{
cookieToken = tokens[0].Trim();
formToken = tokens[1].Trim();
}
}
AntiForgery.Validate(cookieToken, formToken);
}
Also take a look at this question AngularJS can't find XSRF-TOKEN cookie
This solution isn't secure since CSRF attacks are still possible as long as the Auth cookie is valid. Both the auth and the xsrf cookie will be sent to the server when an attacker makes you perform a request via another site, and therefore you are still vulnerable until the user does a "hard" logout.
Each request or session should have its own unique token to truly prevent CRSF attacks. But probably the best solution is to not use cookie based authentication but token based authentication such as OAuth. This prevents other websites from using your cookies to perform unwanted requests, since the tokens are used in http headers instead of cookies. And http headers are not automatically send.
Token Based Authentication using ASP.NET Web API 2, Owin, and Identity
AngularJS Token Authentication using ASP.NET Web API 2, Owin, and Identity
These excellent blog posts contain information of how to implement OAuth for WebAPI. The blog posts also contains great information of how to integrate it with AngularJS.
Another solution might be to disable CORS and only accept incoming requests from whitelisted domains. However this won't work for non-website applications, such as mobile and/or desktop clients. Next to that once your website is vulnerable to a XSS attack the attacker will still be able to forge requests on your behalve.
I think your code is flawed. The whole idea around prevent CSRF is to prevent a unique token on each REQUEST, not each session. If the anti-forgery token is a session persisted value, the ability to perform CSRF still remains. You need to provide a unique token on each request...
Haven't had any problems pointed out with the code, so I consider the question answered.
Related
I’m in web dev since many years ago, but I still do not believe you can easily implement user authorization in front-end apps or browser extensions.
While front-end authentication can be achieved (cookies, jwt, etc.) and works fairly well, the same is not for authorization. The common example is when you want to restrict access to some content and/or functionalities to logged-in users only.
I am inspecting many browser extensions and web apps, and I usually find something like this pseudocode:
if (user.isLogged === true) {
// code to show ui components and actions
} else {
// code to show ui components and actions
}
which is highly insecure.
For instance, this is coming from an extension available on the chrome web store:
function initApp() {
firebase
.auth()
.onAuthStateChanged(function (user) {
if (user) {
// User is signed in. var uid = user.uid; window.location.href = "app.html";
const uid = user.uid;
const name = user.displayName;
getUserData(uid);
// Plus a lot of other code to show/hide ui components and actions
} else { }
document
.getElementById('quickstart-button')
.disabled = false;
});
document
.getElementById('quickstart-button')
.addEventListener('click', startSignIn, false);
}
The only secure way is to actually load HTML and JS chunks dynamically only when and if the user is authorized to access that page/functionality, where the server decides which chunks serve to the current user based on his role, and then those chunks are injected at runtime. But this is, on one hand, failing with the spa architecture itself because it's very near to serving the app server-side (it's never the client to decide what to show). It also requires injecting HTML markup dynamically using JS which is not ideal for security. Plus, this is something not trivial to implement, so I guess the majority of js apps out there are handling restricted content/areas using the paradigm shown above, based on the current in-memory/store (or in cookie/storage) state of the current user, which is highly insecure and can be easily manipulated by the end-user. Especially in browser extensions where you are not even allowed to obfuscate the code.
Am I missing something?
TL;DR you are right, don't rely on front-end security. There is none. Grab the secret content from a backend and require authentication.
You mentioned SPAs so let's analyze the situation there.
Assume you have a React App with a main component looking similar to this:
function App() {
const { loggedIn, isSpecialUser } = useContext(authContext);
if (loggedIn)
return <MainPage />;
if (isSpecialUser)
return <SuperSecretPage />;
return <LoginPage />;
}
The loggedIn and isSpecialUser state is dynamically set after the user logged in (or relogin by cookies, jwt, whatever).
Therefore the restricted areas for MainPage and SuperSecretPage will only show after the user logged in.
So what if the user looks at the source of the SuperSecretPage?
He should see something like this:
function App() {
const { token } = useContext(authContext);
const { data } = useApi(getSuperSecretContent, token);
return <div>{data}</div>;
}
Even if the super secret content is static for all user, it should be fetched with a separate call that requires a token. Access rights must be checked by the backend. If you just write your content in there, it can be seen by everyone.
Edit Response to OP's comment. He wrote
a malicious user has at least 2 options: 1) change the js content of the main App component, to let's say return ; when the user is not logged in at all; 2) manually change the ajax callback to set his status to Superuser regardless of the actual response.
For both ways, the user would just break the clients rendering but not the security. In any case, the client must make an API-call to receive the secret content:
const { data } = useApi(getSuperSecretContent, token);
useApi is an abstract hook that uses some sort of API (e.g. REST-API) to fetch the content during runtime (ajax). The second parameter is a token. This token must be some sort of authentication token the servers sends the user after login. This token here is the crucial part (see OAuth, JWT). For each ajax call to get the super secret content, a valid token must be supplied. The backend then has to check if the token is valid. If and only if the token is valid, the backend returns the super secret content.
Pseudocode useApi:
function useApi(apiCall, token) {
make ajax request {
endpoint: apiCall,
authorization header: token
}
if success:
return data
else if auth error:
return error
}
Pseudocode backend:
get request to super secret content:
if token is not valid:
return auth error;
else
return super secret content
The user cannot trick the backend into sending him the super secret content by manipulating the frontend state.
Edit 2 Response to OP's confusion about authentication and authorization.
You must use the token also for authorization. In case of a JWT for instance, the token itself can include infos about rights management, otherwise you could query access rights from a database for a given user.
This must happen during the request handling #backend side.
Pseudocode backend e.g.:
get request to super secret content:
get access rights for token
if access rights include super secret resources:
return super secret content;
else
return auth error;
I 'm new to this forum and I expect a lot of help from the experts, we have a project where OWIN authentication is implemented in C# and we have internal tool for UI(in old angular language, javascript) there seems to be lot of flaws, below are some issues.
If access token gets expired and there is a call to access protected resource, here the call won't complete so how can we make that call complete post generating a new access token using refresh token, this needs to be handled in UI or service side, any code pointers ?
Is it a good practice to generate access token using refresh token before access token expiry interval in UI?
Api's are also consumed by windows service so if access token expires and the service hit's any api with the expired token it will throw unauthorized however same question how to create token on fly and make that call complete.
Any help on this, would be really greatful so, awaiting for your replies, sample code shown below.
public class SimpleRefreshTokenProvider : IAuthenticationTokenProvider
{
private static ConcurrentDictionary<string, AuthenticationTicket> _refreshTokens = new ConcurrentDictionary<string, AuthenticationTicket>();
public async Task CreateAsync(AuthenticationTokenCreateContext context)
{
var guid = Guid.NewGuid().ToString();
_refreshTokens.TryAdd(guid, context.Ticket);
// hash??
context.SetToken(guid);
}
public async Task ReceiveAsync(AuthenticationTokenReceiveContext context)
{
AuthenticationTicket ticket;
if (_refreshTokens.TryRemove(context.Token, out ticket))
{
context.SetTicket(ticket);
}
}
public void Create(AuthenticationTokenCreateContext context)
{
throw new NotImplementedException();
}
public void Receive(AuthenticationTokenReceiveContext context)
{
throw new NotImplementedException();
}
}
// Now in my Startup.Auth.cs
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/api/token"),
Provider = new ApplicationOAuthProvider(PublicClientId,UserManagerFactory) ,
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(2),
AllowInsecureHttp = true,
RefreshTokenProvider = new RefreshTokenProvider() // This is my test
};
1) I cannot really provide any code pointers since I don't know your Angular version, but the way I have done it up until now is having some sort of Interceptor for your requests. It looks for any Unauthorized status codes coming from the API, caches the request, tries to hit the refresh token endpoint and resends the cached request with the new access token if the refresh request was successful, if the refresh request failed, ask the user to authenticate again.
You can also try to implement an interceptor that inspects the access token expiry date and hits the refresh token endpoint if it has 5 minutes of validity left.
2) I'd say that refresh tokens are typically used for already expired access tokens.
3) I don't know how much control you have over those Windows Services, but to my knowledge, the refresh token flow is supposed to be handled on the client-side.
I'm making an OAuth2 call from my ASP.NET MVC web application to my Web API through JavaScript to authenticate my user and get a token. The Web API accesses a SQL Server database where the user's login is stored using Identity and the typical AspNetUsers tables. My API call returns a 20 min access token and a 2 week refresh token. The API and consuming apps are products that we are developing that our customers will register with. In other words, all the code is on our side.
I know I must refresh the access token before it expires by passing the API the refresh token. My question is...where do I store the access and refresh tokens on the client for use in my JavaScript to make subsequent API calls or to refresh the token? People online are saying that storing anything client-side is bad, cookies are unsecure, etc and without offering any solutions. Local storage? But of course these are Ajax calls in JavaScript that we are making to the API, so the tokens needs to exist somewhere on the client side! It's driving me crazy trying to figure this out. I know I need to at least use HTTPS.
I'd suggest you to create a table in database to store the refresh token and access token.
Table structure will look like below
ID,Access_Token,Refresh_Token,LastUpdated_Time
Whenever you're calling a API with access token , please check the current time and LastUpdated_Time of token , if it is more than one hour your token will become invalid, so you need to get another valid token using your refresh token.
In my application , I had 55 minutes lifespan of toke, after that time token gets invalid.
Code
if (dateTimeDiff > 55) {
var request = (HttpWebRequest) WebRequest.Create("https://www.googleapis.com/oauth2/v3/token");
var postData = "refresh_token=your refresh token";
postData += "&client_id=your client id";
postData += "&client_secret=your client secret";
postData += "&grant_type=refresh_token";
var data = Encoding.ASCII.GetBytes(postData);
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
request.ContentLength = data.Length;
request.UseDefaultCredentials = true;
using(var stream = request.GetRequestStream()) {
stream.Write(data, 0, data.Length);
}
var response = (HttpWebResponse) request.GetResponse();
string responseString = new StreamReader(response.GetResponseStream()).ReadToEnd();
}
The response will contain new access token, don't forget to update your table with LastUpdated_Time and new token.
The auth token is something that you can always obtain using the refresh token. So, have an interceptor that validates the incoming and outgoing requests and store the auth-token there. Coming to the refresh-token, in our application we were initially storing in cookies, later moved to local storage.
You can tried to store the token on the localStorage, what I am doing right now is(I am using typescript and react, but I think it will give a clue for your web app):
const msg = await login({ ...values, type });
let accessToken = msg.accessToken;
window.localStorage.setItem("x-access-token", accessToken);
I'm having a Single Page Application written in AngularJs (The framework is irrelevant at this point) The application is hosted in IIS and it's compose of index.html plus a bunch of client assets.
On backend I have WebApi 2, hosted also in IIS as a separate application.
For authentication on client I'm using Firebase (simple login) with several social netoworks enabled, like Facebook, Twitter or Google.
So far so good. I like how easy it is to enable twitter authentication for example with firebase.
On login with social network i get back from firebase, the firebaseAuthToken and provider accesstoken.
Now I want to use firebaseAuthToken or provider access token to authenticate with my WebApi.
The question is: What is the best way to authenticate with WebApi in given conditions?
There is not an option to use only firebase to store my data and get rid of web api since I have in place complex business logic on server.
One silly idea that i have so far, is to pass the social provider access token to the server, validate the token against provider and then issue a security token using Owin -Katana.
I'm not using build in social providers support from katana due to lack of documentation, complexity and bad integration with single page apps. I found the visual studio template for SPA too mvc specific. But that's me :)
tl;dr - Demo Project on GitHub
The steps below may seem long, but it's actually really easy. I created my demo project in just an hour or so.
I agree with you about using Owin and Katana. I've been through that process before and it wasn't really a great experience. Using Firebase was a heck of a lot easier.
This can all be done with JWTs!
When you authenticate through Firebase and whatever social provider, you get back a JSON Web Token (JWT) - firebaseAuthToken.
Grab your Firebase Secret from the Dashboard
The way JWTs work is that we have a secret token and a client token. The client token is the firebaseAuthToken we receive after logging in. The secret token is generated for us in the Firebase Dashboard.
Store your Firebase Secret in the appSettings section of your Web.config
We need to store this secret key in the Web.config so it's easier to access later.
<add key="FirebaseSecret" value="<Firebase-Secret-Token-Goes-Here" />
Create an Action Filter to check the JWT from the Authorization Header
We can verify the request is valid by passing the client token in the Authorization header. On the server we can store our secret key that we get from our Firebase Dashboard. When the request is checked by Web API we can decode the JWT using a JWT Library (available from NuGet). If the decoding is successful then we can check to token to make sure it isn't expired.
public class DecodeJWT: ActionFilterAttribute
{
public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext)
{
string firebaseAuthToken = string.Empty;
if (actionContext.Request.Headers.Authorization != null) {
firebaseAuthToken = actionContext.Request.Headers.Authorization.Scheme;
} else {
throw new HttpException((int) HttpStatusCode.Unauthorized, "Unauthorized");
}
string secretKey = WebConfigurationManager.AppSettings["FirebaseSecret"];
try {
string jsonPayload = JWT.JsonWebToken.Decode(firebaseAuthToken, secretKey);
DecodedToken decodedToken = JsonConvert.DeserializeObject < DecodedToken > (jsonPayload);
// TODO: Check expiry of decoded token
} catch (JWT.SignatureVerificationException jwtEx) {
throw new HttpException((int) HttpStatusCode.Unauthorized, "Unauthorized");
} catch (Exception ex) {
throw new HttpException((int) HttpStatusCode.Unauthorized, "Unauthorized");
}
base.OnActionExecuting(actionContext);
}
}
Create a $httpInterceptor add the firebaseAuthToken to the header for every request
On the client, the trick is that the token has to be passed every time. To make this easier we need to create a $httpInterceptor with Angular that checks for a firebaseAuthToken on sessionStorage.
.factory('authInterceptor', function ($rootScope, $q, $window) {
return {
request: function (config) {
config.headers = config.headers || {};
if ($window.sessionStorage.firebaseAuthToken) {
config.headers.Authorization = $window.sessionStorage.firebaseAuthToken;
}
return config;
},
response: function (response) {
if (response.status === 401) {
// TODO: User is not authed
}
return response || $q.when(response);
}
};
})
Set the firebaseAuthToken to sessionStorage on a successful login
Whenever a user logs in we can set the value to sessionStorage.
$rootScope.$on('$firebaseSimpleLogin:login',
function (e, user) {
// add a cookie for the auth token
if (user) {
$window.sessionStorage.firebaseAuthToken = user.firebaseAuthToken;
}
cb(e, user);
});
Register the DecodeJWT filter globally
Inside of the WebApiConfig.cs Register method we can set the DecodeJWT filter to apply for all of our ApiControllers.
config.Filters.Add(new DecodeJWT());
Now whenever we make a request to an ApiController it will reject it unless there is a valid JWT. So after a user logs in we can save their data to a ApiController if it already doesn't exist.
// globally uses DecodeJWT
public class UsersController: ApiController
{
// POST api/users
public void Post([FromBody] FbUser user) // See GitHub for this Model
{
// Save user if we do not already have it
}
}
I have a javascript variable that I want to pass back to the server side, which I thereafter intend to use it as an access token to grant user access to other pages which requires this token.
I wonder how do I pass this javascript variable back to server, so I can set it to a session variable? Do I need to send it back using ajax?
this is the part of jQuery I use to retrieve the token from server
$(document).ready(function () {
$('#loginForm').submit(function(e) {
var blargh = $(this).find('input').serialize();
$.ajax({
type: 'post',
url: '/WebAPI/api/authenticate/login',
data: blargh,
success: function (data) {
$.each(data, function(index, token) {
$('#container').prepend('<input type="hidden" name="MY_HIDDEN_FIELD_NAME" id="MY_HIDDEN_FIELD_NAME" value="'+token+'">');
});
},
error: function(jqXHR, status, errorThrown) {
alert("Error " + status + "\nError Thrown" + errorThrown )
},
});
e.preventDefault();
});
});
To pass back an additional item in the AJAX POST, you could add it like this...
var blargh = $(this).find('input').serialize();
blargh.someItem = "value";
Bear in mind that this only works when the form is submitted using AJAX, so not where JavaScript isn't available or is disabled.
All the normal security disclaimers apply!
I would recommend sending you the acess token in request headers when u are sending a ajax request
xhr.setRequestHeader('custom-header', 'value');
and on the server side you can fetch the request header
Couldn't you pass it back as either a hidden form element or pass it back in the query string of a ajax postback?
Example of a hook to get the post back value in global.asmx
protected void Session_Start(object src, EventArgs e)
{
if(!string.IsNullOrEmpty(Request.Form["MY_HIDDEN_FIELD_NAME"]))
{
Session["MY_SESSION_NAME"] = Request.Form["MY_HIDDEN_FIELD_NAME"]
}
}
First - why is your client generating the token (I hope I've understood you correctly there)? The server should generate the token and the client must then be responsible for maintaining it.
If it's an API token that'll only ever be used in the browser from javascript, then I recommend using an authentication cookie - all browsers know how to handle them and you can also easily expire them server-side if you no longer want to allow a particular token to have access (that's quite an important point). Also I strongly recommend against relying on server-side session to maintain the authentication session.
Authentication tokens should ideally be stateless (just like in Forms Authentication's cookie) - the burden of proof is on the client to send you a correct token, with that token containing the information you need to re-initialise the current requests state with the correct user.
If, however, it's a general purpose API for any type of client then you should allow the client to send the token to you in the query string of all requests at a very minimum. You should also support taking it in the request header as well - clients that can easily support setting request headers often prefer to because it then hides the auth token from the URL and makes formatting requests easier (there's also the potential to max out a web server's query string limit if the token is big enough).
I then recommend you look, at a minimum, at overriding MVCs AuthorizeAttribute (there are 2 - one for the 'standard' MVC 4 pipeline and one for the new Web API pipeline, & they would both need to be done if you are using both technologies. The link is for the MVC 4 one) to crack out your cookie/header/query string value. In there you can get the value, decrypt the token, identify the user and set the roles. The core code of that attribute then contains the logic for denying a request based on whether the user is authenticated/has a certain role/is a certain user.