I upload my first ASP.NET MVC application to App Harbor, that uses FormsAuthentication to simple authentication of the user.
As usual on local machine operation that required authentications performes well, at the moment I tun them on AppHarbor, they failed.
I checked that authentication cookie released on access is send with the request, as it visible from image:
and my server side authentication code looks like
FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(
1,
user.UserName,
DateTime.Now,
DateTime.Now.AddDays(1),
true,
user.UserName
);
string encTicket = FormsAuthentication.Encrypt(authTicket);
this.Response.Cookies.Add(
new HttpCookie(
FormsAuthentication.FormsCookieName,
encTicket)
{ Expires = authTicket.Expiration });
I read different posts from AppHarbor blogs like ASP.NET Forms Authentication Considered Broken, it says "harmflul" and not that it's not working.
So, what I do wrong here ?
Related
I am developing an app with .NET Core Web API, Entity Framework and React. I've been reading a lot recently about possible authentication techniques for my API and I've discovered that plain JWT is not entirely secure, so at first I decided to use OpenID Connect with IdentityServer 4. I understand the idea behind OAuth 2.0 and OpenID Connect is to hide user credentials during login process and to involve external authentication provider in issuing an access token, but I don't want to rely on such services because not everyone have an account on Facebook etc. I consider this as an optional way to login. I want to give users an ability to sign in with just login and password. So what is the best (secure) way to accomplish this in modern web apps?
Having project 1 as Client App, project 2 as API Resources and project 3 as Authorization Service (IdentityServer4), I consider following scenarios:
A user is able to create an account on Authorization Service which is responsible for issuing a token required to get access to API Resources through Client App. Authorization Service is registered as authorization provider only for my Client App.
Get authorization token from Authorization Service using resource owner password grant - this one is not recommended by the specs but in my case since user must provide credentials to Authorization Service anyway and I will be hosting every project I can't see any problem.
Don't bother with OAuth and implement authorization mechanism using ASP.NET Core Identity + bearer token authentication.
Any ideas or recommendations highly apprecieated.
I use the JwtBearer package, wire it up in your Startup.cs Configure method like
.UseJwtBearerAuthentication(new JwtBearerOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
TokenValidationParameters = new TokenValidationParameters
{
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(Configuration["AppSettings:AuthConfig:SecretKey"])),
ValidateIssuer = true,
ValidIssuer = Configuration["AppSettings:AuthConfig:Issuer"],
ValidateAudience = true,
ValidAudience = Configuration["AppSettings:AuthConfig:Audience"],
ValidateLifetime = true,
}
})
and my login action on my User controller looks like
[HttpPost]
public string Post([FromBody]LoginRequest request)
{
var contact = dbContext.Contacts.Where(c => c.Active && c.Email == request.Email).Select(c => new { c.Id, c.PasswordHash }).SingleOrDefault();
if (contact == null || !Security.PasswordHash.ValidatePassword(request.Password, contact.PasswordHash))
{
return string.Empty;
}
var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(appSettings.AuthConfig.SecretKey));
var now = DateTime.UtcNow;
var claims = new Claim[]
{
new Claim(JwtRegisteredClaimNames.Sub, contact.Id.ToString()),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Iat, DateTimeOffset.Now.ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64)
};
var jwt = new JwtSecurityToken(
issuer: appSettings.AuthConfig.Issuer,
audience: appSettings.AuthConfig.Audience,
claims: claims,
notBefore: now,
expires: now.AddDays(30),
signingCredentials: new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256));
jwt.Payload.Add("roles", dbContext.ContactRoles.Where(cr => cr.ContactId == contact.Id).Select(ur => ur.Role.Name).ToArray());
return new JwtSecurityTokenHandler().WriteToken(jwt);
}
I use a JWT package for Angular on the client, there may be something similar for React.
I have an MVC 5 web application and can login with a Login.cshtml page and get a cookie and the login works fine. But, I would like to do a login with the Web API and then (maybe) set a cookie so that I am logged in for my MVC pages... (or login with the MVC login and then access the Web API) however the web api returns a bearer token and not a cookie token... so this doesn't work. Is there a way to combine the use of authentication both for my MVC pages and for my Web API pages?
UPDATE:
This isn't really a code issue, more of a conceptual issue.
Normal MVC web pages examine a cookie named, by default, ".AspNet.ApplicationCookie" to determine the requesters identity. This cookie is generated by calling ApplicationSignInManager.PasswordSignInAsync.
WebAPI calls, on the other hand, examine the requests headers for an item named Authorization... and uses that value to determine the requesters identity. This is returned from a WebAPI call to "/Token".
These are very different values. My website needs to use both MVC pages and WebAPI calls (to dynamically update those pages)... and both need to be authenticated to perform their tasks.
The only method I can think of is to actually authenticate twice... once with a WebAPI call and again with the Login post. (see my Answer below).
This seems very hacky... but I don't understand the authorization code enough to know if there is a more proper way of accomplishing this.
The best way to achieve this, is to have an authorization server (a Web API generating a token) and token consumption middle-ware in your MVC project. IdentityServer should help. However I have done it like this:
I built an authorization server using JWT with Web API and ASP.Net Identity as explained here.
Once you do that, your Web APIs startup.cs will look like this:
// Configures cookie auth for web apps and JWT for SPA,Mobile apps
private void ConfigureOAuthTokenGeneration(IAppBuilder app)
{
// Configure the db context, user manager and role manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationRoleManager>(ApplicationRoleManager.Create);
// Cookie for old school MVC application
var cookieOptions = new CookieAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
CookieHttpOnly = true, // JavaScript should use the Bearer
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/api/Account/Login"),
CookieName = "AuthCookie"
};
// Plugin the OAuth bearer JSON Web Token tokens generation and Consumption will be here
app.UseCookieAuthentication(cookieOptions);
OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
//For Dev enviroment only (on production should be AllowInsecureHttp = false)
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/oauth/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(30),
Provider = new CustomOAuthProvider(),
AccessTokenFormat = new CustomJwtFormat(ConfigurationManager.AppSettings["JWTPath"])
};
// OAuth 2.0 Bearer Access Token Generation
app.UseOAuthAuthorizationServer(OAuthServerOptions);
}
You can find the CustomOAuthProvider and CustomJwtFormat classes here.
I wrote a consumption logic (i.e. middleware) in all my other APIs (Resource servers) that I wanted to secure using the same token. Since you want to consume the token generated by the Web API in your MVC project, after implementing the authorization server, you need to the following:
In your MVC app, add this in startup.cs:
public void Configuration(IAppBuilder app)
{
ConfigureOAuthTokenConsumption(app);
}
private void ConfigureOAuthTokenConsumption(IAppBuilder app)
{
var issuer = ConfigurationManager.AppSettings["AuthIssuer"];
string audienceid = ConfigurationManager.AppSettings["AudienceId"];
byte[] audiencesecret = TextEncodings.Base64Url.Decode(ConfigurationManager.AppSettings["AudienceSecret"]);
app.UseCookieAuthentication(new CookieAuthenticationOptions { CookieName = "AuthCookie" , AuthenticationType=DefaultAuthenticationTypes.ApplicationCookie });
//// Api controllers with an [Authorize] attribute will be validated with JWT
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Passive,
AuthenticationType = "JWT",
AllowedAudiences = new[] { audienceid },
IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
{
new SymmetricKeyIssuerSecurityTokenProvider(issuer, audiencesecret)
}
});
}
In your MVC controller, when you receive the token, de-serialize it and generate a cookie from the access token:
AccessClaims claimsToken = new AccessClaims();
claimsToken = JsonConvert.DeserializeObject<AccessClaims>(response.Content);
claimsToken.Cookie = response.Cookies[0].Value;
Request.Headers.Add("Authorization", "bearer " + claimsToken.access_token);
var ctx = Request.GetOwinContext();
var authenticateResult = await ctx.Authentication.AuthenticateAsync("JWT");
ctx.Authentication.SignOut("JWT");
var applicationCookieIdentity = new ClaimsIdentity(authenticateResult.Identity.Claims, DefaultAuthenticationTypes.ApplicationCookie);
ctx.Authentication.SignIn(applicationCookieIdentity);
Generate a machine key and add it in web.config of your Web API and ASP.Net MVC site.
With this, a cookie will be created and the [Authorize] attribute in the MVC site and the Web API will honor this cookie.
P.S. I have done this with a Web API issuing JWT (Authorization server or Auth & resource server) and was able to consume it in an ASP.Net MVC website, SPA Site built in Angular, secure APIs built in python (resource server), spring (resource server) and an Android App.
Ugg... what I had to do was use the Login.cshtml form and override the submit... make an Ajax call to get the WebApi bearer token... and then do the form submit to get the actual MVC cookie. So, I'm actually making two login requests... one for the WebApi token and the other for the MVC cookie.
Seem pretty hacky to me... it would be nice if there was some way to sign in to MVC using the bearer token... or a call to the WebApi that would return me a cookie that I can use for normal MVC page requests.
If anyone has a better way I would love to hear it.
This is script code that I added to Login.cshtml:
$(document).ready(function () {
$('form:first').submit(function (e) {
e.preventDefault();
var $form = $(this);
var formData = $form.serializeObject(); // https://github.com/macek/jquery-serialize-object
formData.grant_type = "password";
$.ajax({
type: "POST",
url: '#Url.Content("~/Token")',
dataType: "json",
data: formData, // seems like the data must be in json format
success: function (data) {
sessionStorage.setItem('token', data.access_token);
$form.get(0).submit(); // do the actual page post now
},
error: function (textStatus, errorThrown) {
}
});
});
});
I assume what you're trying to do is have pages served by MVC have javascript that makes calls to Web API methods. If you're using ASP.NET Identity to handle authentication (which it looks like you're doing), then MVC should be using OAuth tokens that can be passed to Web API for authentication.
Here's a snippet from some javascript code that works for me in a similar situation:
var token = sessionStorage.getItem('access_token');
var headers = {};
if (token) {
headers.Authorization = 'Bearer ' + token;
}
$.ajax({
type: <GET/POSt/...>,
url: <your api>,
headers: headers
}).done(function (result, textStatus) {
I have similar case with you, but i use a different way to authenticate.
I have a web and an api, which all for intranet users. I do not use user's identity to pass web and api. Instead, i created a individual web account, and every time web will use this special account to connect to api.
Because, we also need to make sure that users should not connect to api directly. They should only connect to web ui.
Hope this help you.
From your comments above, from what I understand, you have a scenario wherein you perform login through browser but also have to invoke web-api methods using ajax calls.
The browser calls are session-cookie based. While the ajax calls from the browser would have the session cookie in the header, what is required is the authentication header to be present for the web-api to perform validation.
So on a successful login you'd also have to generate a web-api based token, set it as a cookie (that is accessible by javascript) and then while making ajax calls, pick it up from the cookie and include it as header in your 'Authorization' header.
I have a multi-tenanted application with several clients, who are distinguished by subdomain:
client1.mydomain.com
client2.mydomain.com
etc
I'm using forms authentication and the ASP.NET Auth & Session cookies on the client are set for the subdomain, e.g. client1.mydomain.com. This means that if I browse to client2.mydomain.com then I'm not logged in and the browser doesn't post the client1 cookies. Which is as it should be.
However something that has been picked up by our security testing is that you can take the cookie values from client1 and use the values to create cookies for client2 (we've done this in firebug). ASP.NET accepts these cookies and thinks you're authorised on client2.
How can I configure ASP.NET so that this doesn't happen?
The forms element in web.config allows you to set domain but I can't use this as I've a multi-tenanted app. I'm setting the cookie with
FormsAuthentication.SetAuthCookie(userName, false);
but I don't see a why to limit this to subdomain.
You should add the domain name to the user data of the cookie. To do this you have to switch to another cookie api:
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
... other parameters ..., domain );
HttpCookie cookie = new HttpCookie( FormsAuthentication.FormsCookieName );
cookie.Value = FormsAuthentication.Encrypt( ticket );
Response.SetCookie( cookie );
Then, in your global application class have an event handler that fires after the identity is established for the request. In the handler, verify that the domain name in the cookie is equal to a domain of the current request:
public void Application_PostAuthorizeRequest( object sender, EventArgs e )
{
HttpApplication app = sender as HttpApplication;
HttpContext ctx = app.Context;
if ( ctx.User.Identity.IsAuthenticated )
{
// current domain
string currentDomain = ctx.Request.Url.... // get the domain
// domain from cookie
FormsIdentity id = (FormsIdentity)ctx.User.Identity;
FormsAuthenticationTicket ticket = id.Ticket;
string cookieDomain = ticket.UserData;
if ( currentDomain != cookieDomain )
throw new Exception( "break the execution of the current request" );
...
This check will validate if the cookie was issued for the current domain or rather someone tries to reuse cookies between different domains.
We're building a commandline application that needs some data from the IIS server using forms authentication. How can I do forms authentication from a commandline application? I've tried but all that happens is that the request gets redirected to the login page.
I'm guessing that if I could include an authentication cookie the request with the right credential, the download would be fine.
I have control over both client and server. I can set the the machineKey in web.config, but can't figure out how to set this in the commandline application. This has to be the same validationKey to encrypt the cookie in the right format?
The server is written in asp-net mvc.
var request = (HttpWebRequest)WebRequest.Create(uri);
var formsCookiePath = "/";
var cookieName = ".FormName";
var domain = "localhost";
var username = "username";
var password = "pa$$word";
var ticket = new FormsAuthenticationTicket(1,
username,
DateTime.Now,
DateTime.Today.AddYears(10),
true,
password,
formsCookiePath);
var encryptedTicket = FormsAuthentication.Encrypt(ticket);
var authenticationCookie = new Cookie(
cookieName,
encryptedTicket,
formsCookiePath,
domain)
{
HttpOnly = true,
Secure = false
};
request.CookieContainer = new CookieContainer();
request.CookieContainer.Add(authenticationCookie);
request.UserAgent = "Internal downloader";
var loWebResponse = (HttpWebResponse)request.GetResponse();
Update:
Based on the answer from Zruty I used this example to generate the authentication cookie:
ASP.NET Authorization from Console Application & Timeout
You want to mimic the authentication code locally on your client? I think you're going the wrong way.
You should instead leave the server work (cookie generation) to the server. Make two subsequent requests: one with your login information (the server will generate you a cookie), and another one with the supplied cookie.
Stackoverflow uses OpenId as many other websites. However, I rarely need to provide my OpenId to Stackoverflow while with other OpenId enabled websites, I have to do it once a day or week.
This suggests to me that the expiry of the session is with the website and not the OpenId provider.
Looking at the DotNetOpenId code in ASP MVC, I can see that after a successful authentication by the OpenId provider FormsAuthentication.SetAuthCookie is called with the identifier and a boolean parameter to determine if the cookie should be persisted.
How can I force this cookie to expire, say in 2020 instead of whatever the default value is.
From MSDN - Explained: Forms Authentication in ASP.NET 2.0:
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1,
"userName",
DateTime.Now,
new DateTime(2020, 01, 01), // value of time out property
false, // Value of IsPersistent property
String.Empty,
FormsAuthentication.FormsCookiePath);
string encryptedTicket = FormsAuthentication.Encrypt(ticket);
HttpCookie authCookie = new HttpCookie(
FormsAuthentication.FormsCookieName,
encryptedTicket);
authCookie.Secure = true;
Response.Cookies.Add(authCookie);
You can use FormsAuthentication.RenewTicketIfOld() to update the ticket. This method would check if the current ticket has expired and if not, to extend its expiration. You can call this method on every request to your site, which will "slide" th expiration further in future as the user keeps using your site.