Hi I am trying to get a hang of how the new authentication mechanism works in MVC5 in the SPA template and I seem to be left confused.My end goal is to create an API that will be exposed to a SPA , iOS , Android and Windows Phone clients
Here is what I understand:
I understand that somehow at startup the class decorated with:
[assembly: OwinStartup(typeof(WebApplication1.Startup))]
is magicly calling ConfigureAuth method:
Inside this method I have 3 lines of code and inside the startup class constructor I have initialized the OAuth authentication options:
static Startup(){
PublicClientId = "self";
UserManagerFactory = () => new UserManager<IdentityUser>(new UserStore<IdentityUser>());
OAuthOptions = new OAuthAuthorizationServerOptions {
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(PublicClientId, UserManagerFactory),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
AllowInsecureHttp = true
};
}
public void ConfigureAuth(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
app.UseOAuthBearerTokens(OAuthOptions);
}
The first two lines in ConfigureAuth seem to set my application and external application to use cookies for storing authentication state, while the third seems to state that it is using bearer tokens for my application.
From what limited knowledge I have so far about mobile devices native apps do not understand cookies and I should use tokens for authentication.
If that is the case shouldn't the externalSignIn be set to Bearer tokes instead of external cookie?
While debugging I also noticed that in the OAuthProvider the authentication type is actually set to bearrer tokens.If that is the case what does this line of code actualy do:
app.UseCookieAuthentication(new CookieAuthenticationOptions());
Some clarification to how this works would be grattely appreciated I could only find information online that shows me how tu use external logins.
It seems to me that the MVC 5 SPA Template is a demonstration of what is possible more than a commitment to a particular best practice.
I have found that removing the line app.UseCookieAuthentication(new CookieAuthenticationOptions()); has no effect on the SPA at all because, as is typical with SPAs, all HTML needed is retrieved anonymously and all authentication is, thereafter, done on any subsequent requests for data. In this case data would be retrieved from WebAPI endpoints and protected with Bearer Tokens.
I don't know why it has been done this way. There are a number of other areas like this where two different concerns are a bit muddled. for example the traditional Global.asax MVC Application_Start is still in place but the newer OWIN Startup mechanism is also present. There is no reason why everything in Application_Start (Filter / Route / Bundle registration, etc.) couldn't have been handled in OWIN Startup.
There are other issues too. If you turn on External Auth (e.g. with Google) and then reduce the AccessTokenExpireTimeSpan, you'll find that when the Token has expired your SPA presents a 'Authorization has been denied for this request.' message. In other words, there is no mechanism in place for Token refreshes. This is not immediately apparent out of the box because the Access Token timeout is set to 14 days, which is rather insecure when considering Cross-Site Request Forgery attacks and the like. Furthermore, there is no enforcement of a transport security mechanism, such as SSL. Tokens are not inherently secure and need to be secured in transport to prevent CRSF attacks and data being extracted en route.
So, MVC 5 SPA is good as a demo, I think, but I wouldn't use it in production. It shows what the new OWIN Middleware can do but it is no substitute for a comprehensive knowledge of Token-based security.
Related
I used the MVC5 web template to create a new site with Individual User authentication and when I try to run it I get:
System.InvalidOperationException: 'A claim of type
'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier'
or
'http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider'
was not present on the provided ClaimsIdentity. To enable anti-forgery
token support with claims-based authentication, please verify that the
configured claims provider is providing both of these claims on the
ClaimsIdentity instances it generates. If the configured claims
provider instead uses a different claim type as a unique identifier,
it can be configured by setting the static property
AntiForgeryConfig.UniqueClaimTypeIdentifier.'
I haven't changed anything in the code since it was generated. What could be causing this?
So the answer to this turned out to be to clear the cookies for the site.
As far as I can tell, the issue occured because I was also developing another MVC5 app at the same time, and that one was using a different set of authentication code (Active Directory based).
I worked out that the two apps were interfering with each other by commenting out the #Html.AntiForgeryToken() line in the _LoginPartial class and then the home page loaded without the error. What I then saw was that I was already logged in, even though this was the first run of the app.
Clearing the cookies sorted that issue, but I definitely wasn't expecting two different MVC apps to share a cookie. However, that is actually the expected behaviour, because by default the ASP.NET Cookie Authentication will create a cookie named .AspNet.ApplicationCookie for every app. If you inspect the cookies for your ASP site you can see this:
That's actually very easy to change, just modify the code in Startup.Configuration to set a specific CookieName:
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "ApplicationCookie",
LoginPath = new PathString("/Account/Login"),
CookieName = "yourCookieName"
});
}
}
Then, clear the cookies for the site, run it up again and you should see the Cookie has now been renamed.
My applications needs to support Google+ login only. No internal users in a database or anything extra. I was able to configure the stuff so that the following action is hit by Google with identity information:
[AllowAnonymous]
public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{
ExternalLoginInfo loginInfo = await HttpContext.GetOwinContext().Authentication.GetExternalLoginInfoAsync();
if (loginInfo == null)
{
return RedirectToAction("Login");
}
// what do I do here ?????????
}
My understanding is that I need to somehow store claims returned by Google into a session cookie but I don't know what to do. Should I convert ExternalLoginInfo into IPrincipal first? How do I have the principal instantiated on the subsequent requests?
My initialization code is:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login")
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
// bla-bla-bla
app.UseGoogleAuthentication(googleAuthOptions);
If you don't want any of the Identity tables, I don't think Identity is a good fit for you. It persists the claims and logins in the database, and I don't think there's much you can do about that.
In this one narrow scenario, it is actually probably better if your roll-your-own. Just follow the Google API docs and implement their login manually. You're essentially outsourcing all the authentication at this point, so there aren't a whole lot of security implications from not relying on a more standard authentication framework like Identity. You may even be able to find a Nuget package that just does Google Auth.
You don't need to do anything.
They authenticated on the client side (which gave them a cookie), then
You set up Owin middleware correctly for google on your backend.
This has taken care of everything.
I believe that the Owin middleware does not go and automatically register users into a database for you.
Depending on the scopes you asked for on the front end, it will be able to give you some details of the user which are in the principle. (MS converts them to Claims for you).
I've been looking for a way to integrate the Azure ACS home realm discovery page into our ASP.Net MVC5 app login page rather than use the default one hosted on ACS itself.
What I want is something like what is suggested here:
http://www.cloudidentity.com/blog/2014/11/17/skipping-the-home-realm-discovery-page-in-azure-ad/#comment-126567
I’m building an MVC 5.1 on .Net 4.5.1 Azure Web Role which needs to authenticate users from multiple corporate identity providers – some with AAD, some with ADFS – and the list will grow over time. Initially this has been simple enough to set-up with Azure ACS as the federation provider. It presents a home realm discovery (HRD) page and the flow works.
Things become complicated when I try and follow the instructions for adding the HRD into my login page directly (following the instructions given in the ACS application integration pages using the Home Realm Discovery Metadata Feed and some example HTML + JS). I am able to present the HRD buttons as I would like however I am struggling with wiring the sign in process into the OWIN WsFederation setup, which in my application currently only knows about the ACS WsFederationMetadataUrl.
I’ve currently got this in my ConfigureAuth method:
app.UseWsFederationAuthentication(wsFederationOptions: new WsFederationAuthenticationOptions()
{
Notifications = new WsFederationAuthenticationNotifications()
{
RedirectToIdentityProvider = (context) =>
{
context.ProtocolMessage.Whr = ".com";
return Task.FromResult(0);
}
},
MetadataAddress = CloudConfigurationManager.GetSetting("Authentication.WsFederationMetadataUrl"),
Wtrealm = CloudConfigurationManager.GetSetting("Authentication.Realm"),
AuthenticationMode = AuthenticationMode.Passive,
});
Where .com is the domain of one of the IdPs in my ACS which happens to be our own AAD. But this doesn’t work. Can this scenario be made to work? I found one relevant stack post which talks about skipping the home realm discovery with Ws-Federation OWIN Middleware and allows for the Whr parameter to be set via a user action but so far I haven’t been able to get it to work with the Whr hard coded.
Skipping home realm discovery with Ws-Federation OWIN Middleware
I’ve changed the buttons from the example HTML+JS so that they post to the /Account/ExternalLogin controller action and get into the OWIN pipeline that way:
// POST: /Account/ExternalLogin
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult ExternalLogin(string provider, string homeRealm, string returnUrl)
{
// Request a redirect to the external login provider
return new ChallengeResult(provider, homeRealm, Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl }));
}
rather than use a cookie and navigate to the login page at the chosen home realm (as the boiler plate HTML+JS from the ACS does) :
// Sets a cookie to remember the chosen identity provider and navigates to it.
function IdentityProviderButtonClicked() {
SetCookie(this.getAttribute("name"));
window.location = this.getAttribute("id");
return false;
}
I've got several applications hosted on the same IIS (different context roots), that are all protected using the Microsoft.ASPNet.Identity 2.1 NuGet packages. At the moment however, when I log in to one of the applications, the next visit to any of the other applications prompts me to log in again. I can't be logged in to more than just one of the applications at once.
I'm guessing that they are all using the same cookie to store the login token, so when you log in on one app, it resets the cookie which becomes invalid for requests to the other applications.
What are my options for resolving this? I don't mind having to log in to each app individually, so can each app be configured to use a different cookie?
Alternatively, each app does in fact share the same User table in the DB, so it might be possible to configure it so that when you log in to one of the applications, the others also become logged in.
Have a different cookie name for each app:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
CookieName = "CookieNameHere",
});
As shown on this page http://tech.trailmax.info/2014/07/rename-authentication-cookie-name-of-asp-net-identity/
Yes, this is because on 'localhost' you are sharing the same cookie.
This will not happen on production, because cookies are domain only. (unless, of course, all applications are deployed to same domain).
This is kinda annoying on localhost but easy to solve. Just change the name of the cookie for each application.
This varies from identity version to version but something like this is what you are looking for :
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
CookieName = "MyApp1", // <-- add this, with different names for each app
// ...
});
normally found on Startup.Auth.cs or Startup.cs file.
As of using the same cookie on all applications (if they share subdomain.domain) you need to get MachineKey (validationKey, and decryptionKey) AND same cookie name on all your applications.
something like this on web.config:
<machineKey
validationKey="..." <-- some valid validation key
decryptionKey="..." <-- some valid decryption key
validation="SHA1"
decryption="AES"/>
I think Single Sign-On could be your solution. Search for it on Google.
For your start up, you can refer couple of links below:
Single Sign-On Asp.Net
Claim base Single Sign-on for Web and Azure
Single Sign-on for existing MVC App
Hope this is what you are looking for and will resolve your problem.
For my company, I have to make a POC to check if we can use wsFederation authentication for our project, which has a MVC app, some webapi controllers, and some signalR hubs, all in differents projects.
We'd also like to use the OWIN authentication middleware in both the client apps and the Identity provider app.
I use Thinktecture Identity Server v2 as the Identity provider for a start (but we'll have to develop ou own at some point).
For the MVC app, it's pretty straight forward and it works fine, using a SAML2 token.
But now things get a bit more complicated as I'd like an authenticated user on the web app to be able to call a controller method from the web api app (which is different of the MVC one, remember), using ajax calls.
I've read many things about delegating and actAs tokens, but I'm a bit lost and don't where or how to start this part. Also, i can't find anything about delegation using OWIN authentication.
So my first question is : is it possible to achieve this ?
And then : could someone point me in the right direction?
I followed Vittorio Bertocci's instructions when I was working on it.
http://www.cloudidentity.com/blog/2013/01/09/using-the-jwt-handler-for-implementing-poor-man-s-delegation-actas/
A couple of notes about it, where it says JWTSecurityTokenHandler, it is now JwtSecurityTokenHandler. It is a small typo, but it is a good way to loose 15 minutes if you are not aware of it.
I was also not able to use the X509 FindByThumbprint section either. I think that I did not have my local certificate registered properly. Once I am at work tomorrow I will post what I had to change in order to get it to work.
Dominick Baier (http://leastprivilege.com/) also does a course on pluralsight called WebApi v2 Security that does a great job talking about how to inject into the security pipeline and setting up the web api project to handle this.
As another option, you could replace the TokenValidationHandler class that Vittorio uses with the Microsoft.Owin.Security.Jwt package and implement the following code in the Startup.cs file.
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
AllowedAudiences = new[] { ConfigurationSettings.AppSettings["ida:Realm"] },
IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
{
new SymmetricKeyIssuerSecurityTokenProvider(
ConfigurationSettings.AppSettings["ida:ValidIssuer"],
ConfigurationSettings.AppSettings["ida:SymmetricKey"])
},
Provider = new OAuthBearerAuthenticationProvider
{
OnValidateIdentity = context =>
{
var identity = context.Ticket.Identity;
return System.Threading.Tasks.Task.FromResult<object>(null);
}
}
});