How to Extend Cookie expiry in .Net Web API HttpActionContext - c#

I'm implementing Angular Project the Service Side I'm using .NET Web API. The Web API is a Stateless, so I'm using Cookie to maintain the Authentication. AuthLogin Controller Checks the Credential is correct, If it is TRUE then it create the Cookie and pass the cookie via HTTP Response. Additionally I added ActionFilterAttribute to validate the User. If any request the Web API received then it triggers the ActionFilterAttribute before entering into the Controller. Their I'm checking, the Request is containing any Cookie and it checks it with the database. Once the Cookie is validated I need to extend the cookie expiry to 30 mins.
My Web API Controller Methods
[HttpPost]
public HttpResponseMessage AuthLogin()
{
serverCookie = new CookieHeaderValue
("Test", "Super");
serverCookie.Expires = DateTimeOffset.Now.AddMinutes(15);
serverCookie.Domain = Request.RequestUri.Host;
serverCookie.Path = "/";
HttpResponseMessage response = Request.CreateResponse(_credential.Item2 ? HttpStatusCode.OK : HttpStatusCode.Unauthorized);
response.Headers.AddCookies(new CookieHeaderValue[] { serverCookie });
return response;
}
[HttpPost]
[Authenticate]
public string SecurePost()
{
return "Success";
}
The ActionFilterAttribute C# Code:
public class AuthenticateAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
Debug.WriteLine("Cookie Life Time Extended Successfully to 30 Mins.");
}
}
My Requirement is to extended the life span of Cookie to 30 Mins and send the information along with the return value. Kindly assist me in this regards.

I was able to extent cookie expiry time in MVC application
HttpCookie cookie = Request.Cookies["MyCookie"];
if (cookie!+null && !cookie.Value.IsEmpty())
{
// Update the cookie expiration
cookie.Expires = DateTime.Now.AddMinutes(Convert.ToInt32(1));
Response.Cookies.Set(cookie);//Request.Cookies.Set(cookie);
}
else
{
}

The HTTP protocol never sends cookie expiration time to the server. So you can not extend cookie expiration time.
Browser will send only cookie name and value to the server. All the other properties like expires, domain, path, httponly can not be accessed once the cookie has been set.
When you assign a Cookie in the response a SetCookie header gets added to the output and that will contain not only the value but also the path and the expires value.
But when the client browser sends the cookie back to the server, it simply includes the cookie name and value only and It does not send path or expires info.

Related

ValidateAntiForgeryToken endpoint attribute usage in Asp.Net core Angular web app per CSRF attack

I have a web application that uses asp.net core (3.1) backend and angular front end (8.2.11). It uses asp.net Identity framework for user authentication. It store authentication token in local storage to be used as authentication header in requests. Everything is working in the sense controller endpoints are only accessible when a user is logged in, if logged out, typing an endpoint directly into browser would be rejected.
I am still not certain if such a setup prevent the Cross-Site Request Forgery (XSRF/CSRF) attacks. I know using cookie to store authentication token is susceptible to CSRF and I tried a little bit with the [ValidateAntiForgeryToken] attribute on some endpoint, it broke those endpoints of course. I know in Razor page, a form is automatically injected with anti-forgery token. So, do I need to set it up in my angular front-end? and if yes, how? (I've searched a bit on the web and the instructions are all over the place, quite messy with no clear consensus).
Step 1
Add a middleware to your middleware pipeline that generates an AntiforgeryToken, and embeds the token in a non-http-only cookie that's attached to the response:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
...
services.AddAntiforgery(options => {
options.HeaderName = "X-XSRF-TOKEN";
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IAntiforgery antiforgery)
{
...;
app.Use((context, next) => {
var tokens = antiforgery.GetAndStoreTokens(httpContext);
httpContext.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken, new CookieOptions() { Path = "/", HttpOnly = false });
});
}
}
I created a little package for this that contains this middleware.
Step 2
Configure your angular app to read the value of the non-http-only cookie (XSRF-TOKEN) through javascript, and pass this value as a X-XSRF-TOKEN header for requests sent by the HttpClient:
#NgModule({
declarations: [...],
imports: [
HttpClientModule,
HttpClientXsrfModule.withOptions({
cookieName: 'XSRF-TOKEN',
headerName: 'X-XSRF-TOKEN'
}),
...
],
providers: [...],
bootstrap: [AppComponent]
})
export class AppModule { }
Step 3
Now you can decorate your controller methods with the [ValidateAntiforgeryToken] attribute:
[ApiController]
[Route("web/v1/[controller]")]
public class PersonController : Controller
{
private IPersonService personService;
public PersonController(IPersonService personService)
{
this.personService = personService;
}
[HttpPost]
[Authorize]
[ValidateAntiForgeryToken]
public async Task<ActionResult<Person>> Post([FromBody] Person person)
{
var new_person = await personService.InsertPerson(person);
return Ok(new_person);
}
}
Step 4
Make sure the requests you're sending have the following type of url as stated here:
/my/url
//example.com/my/url
Wrong url:
https://example.com/my/url
Note
I use Identity Cookie authentication:
services.AddAuthentication(/* No default authentication scheme here*/)
Since the ASP.NET Core Authentication middleware only looks after the XSRF-TOKEN header, and not the X-XSRF-TOKEN cookie, you're no longer susceptible to Cross-site request forgery.
Spoiler
You will notice that right after signing in/out, the first webrequest that's being sent will still be blocked by XSRF protection. This is because the Identity does not change during the lifetime of the webrequest. So when sending the Login webrequest, the response will attach a cookie with a csrf-token. But this token is still generated with the identity from when you weren't signed in yet.
The same counts for sending the Logout webrequest, the response will contain a cookie with a csrf-token as if you're still signed in.
To solve this, you have to simply send another webrequest that does literally nothing, every time you've signed in/out. During this request you'll once again have the correct Identity in order to generate the csrf-token.
logoutClicked() {
this.accountService.logout().then(() => {
this.accountService.csrfRefresh().then(() => {
this.activeUser = null;
});
}).catch((error) => {
console.error('Could not logout', error);
});
}
Same for login
this.accountService.login(this.email, this.password).then((loginResult) => {
this.accountService.csrfRefresh().then(() => {
switch (loginResult.status) {
case LoginStatus.success:
this.router.navigateByUrl(this.returnUrl);
this.loginComplete.next(loginResult.user);
break;
default:
this.loginResult = loginResult;
break;
}
});
});
The contents of the csrfRefresh method
public csrfRefresh() {
return this.httpClient.post(`${this.baseUrl}/web/Account/csrf-refresh`, {}).toPromise();
}
Server-side
[HttpPost("csrf-refresh")]
public async Task<ActionResult> RefreshCsrfToken()
{
// Just an empty method that returns a new cookie with a new CSRF token.
// Call this method when the user has signed in/out.
await Task.Delay(5);
return Ok();
}
This is where I login the user in my own app
Angular provides built-in enabled by default anti CSRF/XSRF protection.
Angular's HttpClient has built-in support for the client-side half of this technique. Read about it more in the HttpClient guide
Note that the CSRF/XSRF protection is enabled by default on the HttpClient but only works if the backend sets a cookie named XSRF-TOKEN with a random value when the user authenticates.

withCredentials Causing Web API Auth To Be Called Twice

the problem I am having is when I send withCredentials: true to my Web API and return Unauthorized, the function is called again which then returns with the browser login form. I just want the function to run once with simple 401 returned.
For example, in my controller:
[Route("search")]
[HttpGet]
public async Task<IHttpActionResult> GetDeposit([FromUri] DepositSearchFilter filter)
{
return Unauthorized();
}
If I set a break-point then return Unauthorized() is executed twice and after the second execution the browser display the login form. However, if I replace Unauthorized with Forbidden(), then the function only runs once and returns.
Additionally, I create my own AuthorizeAttribute to handle the Authentication Token being sent in a cookie. When I add the header [ValidateToken] the same thing happens inside of the custom Attribute. If I return Unauthorized() then OnAuthorization is called again and the browser displays the login form. But If I return Forbidden inside the custom Attribute it runs once.
public class ValidateToken : AuthorizeAttribute
{
public override void OnAuthorization(HttpActionContext actionContext)
{
CookieHeaderValue headers = actionContext.Request.Headers.GetCookies().FirstOrDefault();
CookieState authCookie = headers.Cookies.Where(p => p.Name == "AUTH-TOKEN").FirstOrDefault();
actionContext.Request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authCookie.Value);
base.OnAuthorization(actionContext);
//if (!base.IsAuthorized(actionContext))
//{
// HandleUnauthorizedRequest(actionContext);
//}
}
//protected override void HandleUnauthorizedRequest(HttpActionContext actionContext)
//{
//actionContext.Response = actionContext.ControllerContext.Request.CreateErrorResponse(HttpStatusCode.Unauthorized, "Unauthorized");
//}
}
So, when I send withCredentials then any code that returns Unauthorized runs twice and then the login form in the browser is displayed. I need the code to run only once and return just a 401.
For the browser login form showing, I am returning a 401 along with
WWW-Authenticate:Bearer
WWW-Authenticate:NTLM
WWW-Authenticate:Negotiate
The Web API does not return Basic or Digest. I have other Web API calls in which the response returns the same thing but the Unauthorized code does not execute twice and the login form is not shown. The difference is withCredentials is not sent.
Make sure Anonymous Authentication is enabled and Windows Authentication is disabled. That should remove NTLM and Negotiate
<IISExpressAnonymousAuthentication>enabled</IISExpressAnonymousAuthentication>
<IISExpressWindowsAuthentication>disabled</IISExpressWindowsAuthentication>

ASP .NET Identity - How To Validate Auth Token In Custom Filter Attribute

I have a question about verifying an Auth Token. Currently, I'm generating my Auth Token at /token and sending it back to the client side where I store it in session storage. To make the process as secure as possible I'm looking to store the token in a HttpOnly and Secure cookie to protect against XSS.
I'm sending the cookie along with a XSRF token where on my client side (Angular) I set the X-XSRF-TOKEN header to the XSRF token value. The browser then automatically send the Auth Cookie back to me where I can read the token.
My main question is how do I extract the token and validate it? Before I was using Authentication Bear auth_token with the [Authorize] attribute but that doesn't work now since the token is in a cookie. I made my own Attribute (see below, but I'm stuck on the line where I actually verify the token. I'm not sure what function to call and pass the token to, to validate it.
public class ValidateToken : AuthorizationFilterAttribute
{
public override void OnAuthorization(HttpActionContext actionContext)
{
protected override bool IsAuthorized(System.Web.Http.Controllers.HttpActionContext actionContext)
{
var headers = actionContext.Request.Headers;
CookieHeaderValue authToken = headers.GetCookies().FirstOrDefault();
CookieState authCookie = authToken.Cookies.Where(p => p.Name == "AUTH-TOKEN").FirstOrDefault();
if (authCookie.Value is valid -> need help here) {
actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized);
}
}
}
}
Edit: I've tried the following
actionContext.Request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authCookie.Value);
bool isAuth = base.IsAuthorized(actionContext);
But the RequestContext is already set once OnAuthorization is called and it doesn't update to use the new Header I added.

A way to remove specific cookie from HttpRequestHeaders in WebApi

I'm trying to remove specific Set-Cookie header from HttpResponseHeaders in OnActionExecuted method of ActionFilter.
I'm having few issues with that:
I cannot see the way of enumerate headers. The collection is always
empty, even if I see headers in debugger.
Because I cannot
enumerate, I cannot remove specific header. I can only remove all
headers with the same key, but Set-Cookie can have multiple
entries.
Currently I'm removing all cookies, but this is not what I want.
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
HttpResponseHeaders headers = actionExecutedContext.Response.Headers;
IEnumerable<string> values;
if (headers.TryGetValues("Set-Cookie", out values))
{
actionExecutedContext.Response.Headers.Remove("Set-Cookie");
}
base.OnActionExecuted(actionExecutedContext);
}
From the link:
You cannot directly delete a cookie on a user's computer. However, you can direct the user's browser to delete the cookie by setting the cookie's expiration date to a past date. The next time a user makes a request to a page within the domain or path that set the cookie, the browser will determine that the cookie has expired and remove it.
So, how to remove/delete cookie in ASP.NET Web Api at action filter level, just try to set expiration date of cookie to a past date:
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
var response = actionExecutedContext.Response;
var request = actionExecutedContext.Request;
var currentCookie = request.Headers.GetCookies("yourCookieName").FirstOrDefault();
if (currentCookie != null)
{
var cookie = new CookieHeaderValue("yourCookieName", "")
{
Expires = DateTimeOffset.Now.AddDays(-1),
Domain = currentCookie.Domain,
Path = currentCookie.Path
};
response.Headers.AddCookies(new[] { cookie });
}
base.OnActionExecuted(actionExecutedContext);
}

How to use JWT in MVC application for authentication and authorization?

I planned to use ASP.NET Identity 2.0 in an ASP.NET MVC application for authentication and authorization.
Referring the below link
JSON Web Token in ASP.NET Web API 2 using Owin
I was able to create a access token(JWT) for the valid user i.e., When user Logs in to the application I will validate the user with name and password then I will issue a JSON web token for that valid user.
Now, I read in some articles that we need to pass the bearer token in headers for every request to validate the user for authentication. In MVC we will provide Authorize attribute for the methods that needs to be protected as shown below…
public class UserController : BaseHRAppController
{
[Authorize]
public ActionResult Index()
{
return View();
}
}
How to tell my MVC application to use JWT for validating the user?
I want to make my MVC application validate the user using JWT whenever the user tries to access the method with authorize attribute. Since I will use AJAX calls in many pages to access method present in MVC controller, I don't think it's good to pass a token on every AJAX request. I need help to accomplish authentication and authorization in an efficient way using ASP.NET Identity in an MVC applicaton.
Currently, I don't know how to use this JWT token for authentication and authorization in an MVC application.
In order for MVC to understand anything about your JWT you basically have to tell it :-) . First, install the Jwt package from nuget:
Install-Package Microsoft.Owin.Security.Jwt
Then open up your Startup.cs file and add a new funtion that will tell MVC how to consume JWT. At basics your Startup will look something like:
using System.Configuration;
using Microsoft.Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.DataHandler.Encoder;
using Microsoft.Owin.Security.Jwt;
using Owin;
[assembly: OwinStartupAttribute(typeof(TOMS.Frontend.Startup))]
namespace TOMS.Frontend
{
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
ConfigureOAuthTokenConsumption(app);
}
private void ConfigureOAuthTokenConsumption(IAppBuilder app)
{
var issuer = ConfigurationManager.AppSettings["Issuer"];
var audienceId = ConfigurationManager.AppSettings["AudienceId"];
var audienceSecret = TextEncodings.Base64Url.Decode(ConfigurationManager.AppSettings["AudienceSecret"]);
// Api controllers with an [Authorize] attribute will be validated with JWT
app.UseJwtBearerAuthentication(new JwtBearerAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
AllowedAudiences = new[] { audienceId },
IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
{
new SymmetricKeyIssuerSecurityTokenProvider(issuer, audienceSecret)
}
});
}
}
}
You will notice that I am placing the issuer, audienceId and audienceSecret in my Web.config file. (Those values should match the ones on your Resource Server). Also, you might want to ensure you have an updated System.IdentityModel.Tokens.Jwt running:
Update-package System.IdentityModel.Tokens.Jwt
With those set, you may decorate your controller Action with the [Authorize] attribute and play ball.
Play ball of course meaning fire your request from your javascript to your protected controller action:
//assuming you placed the token in a sessionStorage variable called tokenKey when it came back from your Authorization Server
var token = sessionStorage.getItem(tokenKey);
var headers = {};
if (token) {
headers.Authorization = 'Bearer ' + token;
}
$.ajax({
type: 'GET',
url: 'CONTROLLER/ACTION',
headers: headers
}).done(function (data) {
self.result(data);
}).fail(showError);
UPDATE
By The way, if you wish to add the values in your web.config file in order to retrieve them as I did above; simply add them under the AppSettings:
<configuration>
<appSettings>
<add key="Issuer" value="YOUR_ISSUER" />
<add key="AudienceId" value="YOUR_AUDIENCEID" />
<add key="AudienceSecret" value="YOUR_AUDIENCESECRET" />
</appSettings>
</configuration>
...of course, replacing the "values" with your own
I don't know if you solved this, but I was having a similar issue and decided to store the token using FormsAuthentication which I was able to encrypt the token, and on each request the cookie was passed back and then I could decrypt it to get the JWT and then pull out the roles/claims from and then use those roles to create and Identity Principal that would allow me to decorate my controller methods with [Authorize(Role="blah,blah")].
Here is some sample code below.
Once you get the JSON web token back from the api after login, you can use something like:
var returnedToken = (TokenResponse)result.ReturnedObject;
var ticket = new FormsAuthenticationTicket(1, model.Email, DateTime.Now, ConvertUnitToDateTime(returnedToken.expires_in), true, returnedToken.access_token);
string encryptedTicket = FormsAuthentication.Encrypt(ticket);
var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
cookie.HttpOnly = true;
Response.Cookies.Add(cookie)
I have some of my own created classes and methods in there, but it will give you the general idea that you store the JWT access token as well as the expiration date in your FormsAuthentication cookie.
Then the cookie is passed with each request and in your Global.asax file you can have a method to authenticate the request:
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
HttpCookie authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
//Extract the forms authentication cookie
FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
JwtSecurityToken jwTok = TokenHelper.GetJWTokenFromCookie(authCookie);
// Create the IIdentity instance
IIdentity id = new FormsIdentity(authTicket);
// Create the IPrinciple instance
IPrincipal principal = new GenericPrincipal(id, TokenHelper.GetRolesFromToken(jwTok).ToArray());
// Set the context user
Context.User = principal;
}
}
So that method you would decrypt the cookie to get the JWT access token which you can then decode using the System.IdentityModel.Tokens.Jwt library from Microsoft and then take those roles and ID and generate the principal and identity for the user which creates your user with the roles.
Then those roles can be validated against the [Authorize] attribute.

Categories