I have a OWIN self-hosted application which has a front-end where users can sign up. Nancyfx does the logic for routing and Models, and in the documentation I saw that Nancyfx comes with 3 types of authentication.
Forms (Nancy.Authentication.Forms)
Basic (Nancy.Authentication.Basic)
Stateless (Nancy.Authentication.Stateless)
I've settled on the Stateless Authentication, and following this example I tried to set up a basic form of authentication.
I wanted to expand further on this, using JWT to have some basic info handy and as a form of basic authentication (e.g. client has token so he's verified.), but this is where I run into a few problems.
The way I authenticate Home -> login -> redirect upon success causes my Response.Header.Authorization to be cleared, not allowing me to catch the token in my custom Bootstrapper.
Code:
protected override void RequestStartup(TinyIoCContainer requestContainer, IPipelines pipelines, NancyContext context)
{
AllowAccessToConsumingSite(pipelines);
StatelessAuthentication.Enable(pipelines, requestContainer.Resolve<IStatelessAuthConfigurationFactory>().Config());
}
//Returns ClaimsPrincipal or Null;
public StatelessAuthenticationConfiguration Config()
{
if(_stat == null)
{
_stat = new StatelessAuthenticationConfiguration(VerifyToken);
}
return _stat;
}
Since my authorization header disappears every request, I would need to persist the JWT. I figure it's possible using OWIN environment or Nancy context, but would this be advisable + what would the effect be for a multi-user environment regarding security.
OWIN has it's own authentication Manager that I could use, I've experimented with it, but it tends to provide a cookie upon successful sign in, which it doesn't seem to revoke on Logout. I just ran into a few issues overall with it, so I settled on NancyFx authentication. (not really a problem as a more general remark I suppose)
Thanks in advance for any help!
Regarding (1), if you roll your own redirection after a successful login, consider setting the Authorization header during the redirect, e.g.
return Response.AsRedirect("/").WithHeader("Authorization", token);
It's actually the responsibility of the client to hold the valid JWT token after authentication. Returning it as a cookie (and deleting it upon logout) could make things easier in terms of client-side implementation and avoid the token persistence issue.
Regarding (2), not really, it's not necessary. JWT tokens are self-contained, and that's why they're useful in stateless auth scenarios.
Related
I am trying to implement an Authorization server using IdentityServer4, using the Hybrid Flow.
let's say url is : auth.company.com
To authenticate users, the company uses a simple .NET MVC login/password form.
url : client.company.com/login.html
My question is : how can i plug the authentication system into the IdentityServer4 ?
I've tried adding this :
Startup.cs / ConfigureServices()
services.AddIdentityServer(SetupIdentityServer)
private static void SetupIdentityServer(IdentityServerOptions options)
{
options.UserInteraction.LoginUrl = #"client.company.com/login.html";
options.UserInteraction.LoginReturnUrlParameter = "referrer";
}
But it resulted in too many redirections error between auth server and authentication server
Thank you
I just replied to another question very similar to this so this is a shameless copy and paste of that:
This will not work as the identity server needs to issue its own cookie once authentication has taken place. This cookie is what allows the authorise endpoint to know who is signed in.
The intention of this model is that authentication takes place on the IDP or it’s negotiated with an external provider via a suitable protocol. Therefore the appropriate approach in this case is to move the login UI into your identity server application. It’s entirely up to you exactly how that is done and where it gets it’s data from but it must be your identityserver4 that issues the cookie.
Can anyone point me to some good documentation or provide good information on the best way to implement authentication and authorisation for an ASP.NET Core REST API.I need to authenticating and authorising the app first and then authenticate and authorise the user.
Ideally I want to be able restrict the controller method that an authenticated app and/or user can access.
I am thinking of using AspNet.Security.OpenIdConnect.Serverenter for the App authentication but I am not sure then how best to perform the user authentication. Maybe reuse the OpenIdConnect authentication on a different endpoint for users with a different header to contain the user token.
Once authenticated I am thinking of just using roles base security to restrict which controllers methods can be accessed.
Is this the correct route to solving this problem?
This is actually a tougher question that that it may seem because the type of clients (software clients) that are using the api seem to drive what kind of auth* is needed. For example, in a web application, where the web application needs auth*, then Asp.Net Identity would work with either a token or a cookie. However, if other clients are going to consume the provided services (mobile apps, WUP apps, then it may be easier to implement using token authentication . When I had this problem, I ran into the issue that I had a knowledge gap because I didn't really understand OAuth. I had to get back to basics.
https://alexbilbie.com/guide-to-oauth-2-grants/
https://www.pluralsight.com/courses/oauth2-json-web-tokens-openid-connect-introduction
Most of the tutorials around Asp.Net Identity "Seem" to be geared towards web clients. Although it is possible to find those that are not. With the introduction of asp.net core, the syntax has changed and many of the old tutorials that show combining cookie and token authentication are no longer applicable. Additionally, Web Api is not longer a separated from other project types in Visual Studio making the change even more pronounced. Here are some older tutorials.
http://satvasolutions.com/combine-asp-net-identity-web-api-and-mvc-best-in-a-single-web-app/
http://blog.iteedee.com/2014/03/asp-net-identity-2-0-cookie-token-authentication/
Combine the use of authentication both for MVC pages and for Web API pages?
IdentityServer is a completely valid solution, works with both client credential and Resource owner credentials grant (user,password) and Brock Allen has usually been very responsive in SO under the tag
https://stackoverflow.com/questions/tagged/identityserver4
or on the github site under issues labeled as questions
https://github.com/IdentityServer/IdentityServer4/issues
With identity server, Once again, I had to go back to basics and work through the tutorials to get an understanding of how this would work in my project.
https://identityserver4.readthedocs.io/en/release/intro/big_picture.html
As Brock quickly pointed out to me in another post, asp.net ef identity is a user store and good to use with the resource owner credentials workflow.
For authentication you can use ASP.NET Core Identity that will use the Microsoft.AspNetCore.Identity.EntityFrameworkCore package, which will persist the identity data and schema to SQL Server using Entity Framework Core.
For authorization you can use Role Based Authorization that uses the Microsoft.AspNetCore.Authorization package.
You can also checkout this video for an overview on ASP.NET Core Authorization
I couldn't find any good documentation on this, however I had to achieve the same thing so I coded the rest api myself by modifying the actions in the standard ASP.NET authentication template to REST API equivalents.
For example here is how I worked the login action:
// POST: /Account/Login
[HttpPost("[action]")]
[AllowAnonymous]
public async Task<ReturnValue<ApplicationUser>> Login([FromBody] loginModel login)
{
if (ModelState.IsValid)
{
ApplicationUser user = await _userManager.FindByEmailAsync(login.email);
if (user == null)
{
return new ReturnValue<ApplicationUser>(false, "Login failed, check username and password.", null);
}
// else if (user.EmailConfirmed == false)
// {
// return new ReturnValue<ApplicationUser>(true, "Confirm email address.", null, user);
// }
else
{
// This doesn't count login failures towards account lockout
// To enable password failures to trigger account lockout, set lockoutOnFailure: true
var result = await _signInManager.PasswordSignInAsync(user, login.password, (bool)login.rememberMe, lockoutOnFailure: false);
if (result.Succeeded)
{
return new ReturnValue<ApplicationUser>(true, user);
}
//if (result.RequiresTwoFactor)
//{
// return RedirectToAction(nameof(SendCode), new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
//}
if (result.IsLockedOut)
{
return new ReturnValue<ApplicationUser>(false, "The account is locked out.", null);
}
}
}
else
{
string message = string.Join("; ", ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage));
return new ReturnValue<ApplicationUser>(false, "Invalid login attempt: " + message, null);
}
// If we got this far, something failed in the model.
return new ReturnValue<ApplicationUser>(false, "Login failed.", null);
}
If you call the API from a javascript within a browser the cookies will be loaded and you should be able to make further authorised calls to the API, if you're calling from another type of client, you will want to ensure the CookieContainer is retained for authorized calls.
From this point you can authorize your REST API controllers using [Authorize] decorator through the standard Microsoft libraries: https://learn.microsoft.com/en-us/aspnet/core/security/authentication/identity
Good luck.
Here it is:
A workshop for moving through the various new pieces in ASP.NET Core Authorization:
https://blogs.msdn.microsoft.com/webdev/2016/03/15/get-started-with-asp-net-core-authorization-part-1-of-2/
https://github.com/blowdart/AspNetAuthorizationWorkshop
Please refer following links.
For Asp.net Core
https://stormpath.com/blog/token-authentication-asp-net-core
For API
https://stormpath.com/blog/rest-api-mobile-dotnet-core
I am using OWIN Authentication for a Multi-Tenant ASP.NET MVC application.
The application and authentication sits on one server in a single application but can be accessed via many domains and subdomains. For instance:
www.domain.com
site1.domain.com
site2.domain.com
site3.domain.com
www.differentdomain.com
site4.differentdomain.com
site5.differentdomain.com
site6.differentdomain.com
I would like to allow a user to login on any of these domains and have their authentication cookie work regardless of which domain is used to access the application.
This is how I have my authentication setup:
public void ConfigureAuthentication(IAppBuilder Application)
{
Application.CreatePerOwinContext<RepositoryManager>((x, y) => new RepositoryManager(new SiteDatabase(), x, y));
Application.UseCookieAuthentication(new CookieAuthenticationOptions
{
CookieName = "sso.domain.com",
CookieDomain = ".domain.com",
LoginPath = new PathString("/login"),
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
Provider = new CookieAuthenticationProvider
{
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<UserManager, User, int>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentityCallback: (manager, user) => user.GenerateClaimsAsync(manager),
getUserIdCallback: (claim) => int.Parse(claim.GetUserId()))
}
});
Application.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
}
I have also explicitly set a Machine Key for my application in the root web.config of my application:
<configuration>
<system.web>
<machineKey decryption="AES" decryptionKey="<Redacted>" validation="<Redacted>" validationKey="<Redacted>" />
</system.web>
</configuration>
Update
This setup works as expected when I navigate between domain.com and site1.domain.com, but now it is not letting me login to differentdomain.com.
I understand that cookies are tied to a single domain. But what is the easiest way of persisting a login across multiple domains? Is there a way for me to read a cookie from a different domain, decrypt it, and recreate a new cookie for the differentdomain.com?
Since you need something simple, consider this. In your particular setup, where you really have just one app accessible by multiple domain names, you can make simple "single sign on". First you have to choose single domain name which is responsible for initial authentication. Let's say that is auth.domain.com (remember it's just domain name - all your domains still point to single application). Then:
Suppose user is on domain1.com and you found he is not logged-in (no cookie). You direct him to auth.domain.com login page.
Suppose you are logged-in there already. You see that request came from domain1.com (via Referrer header, or you can pass domain explicitly). You verify that is your trusted domain (important), and generate auth token like this:
var token = FormsAuthentication.Encrypt(
new FormsAuthenticationTicket(1, "username", DateTime.Now, DateTime.Now.AddHours(8), true, "some relevant data"));
If you do not use forms authentication - just protect some data with machine key:
var myTicket = new MyTicket()
{
Username = "username",
Issued = DateTime.Now,
Expires = DateTime.Now.AddHours(8),
TicketExpires = DateTime.Now.AddMinutes(1)
};
using (var ms = new MemoryStream()) {
new BinaryFormatter().Serialize(ms, myTicket);
var token = Convert.ToBase64String(MachineKey.Protect(ms.ToArray(), "auth"));
}
So basically you generate your token in the same way asp.net does. Since your sites are all in the same app - no need to bother about different machine keys.
You redirect user back to domain1.com, passing encrypted token in query string. See here for example about security implications of this. Of course I suppose you use https, otherwise no setup (be it "single sign on" or not) is secure anyway. This is in some ways similar to asp.net "cookieless" authentication.
On domain1.com you see that token and verify:
var ticket = FormsAuthentication.Decrypt(token);
var userName = ticket.Name;
var expires = ticket.Expiration;
Or with:
var unprotected = MachineKey.Unprotect(Convert.FromBase64String(token), "auth");
using (var ms = new MemoryStream(unprotected)) {
var ticket = (MyTicket) new BinaryFormatter().Deserialize(ms);
var user = ticket.Username;
}
You create cookie on domain1.com using information you received in token and redirect user back to the location he came from initially.
So there is a bunch of redirects but at least user have to type his password just once.
Update to answer your questions.
Yes if you find that user is authenticated on domain1.com you redirect to auth.domain.com. But after auth.domain.com redirects back with token - you create a cookie at domain1.com as usual and user becomes logged-in a domain1.com. So this redirect happens just once per user (just as with usual log in).
You can make request to auth.domain.com with javascript (XmlHttpRequest, or just jquery.get\post methods). But note you have to configure CORS to allow that (see here for example). What is CORS in short? When siteB is requested via javascript from siteA (another domain) - browser will first ask siteB if it trusts siteA to make such requests. It does so with adding special headers to request and it wants to see some special headers in response. Those headers you need to add to allow domain1.com to request auth.domain.com via javascript. When this is done - make such request from domain1.com javascript to auth.domain.com and if logged in - auth.domain.com will return you token as described above. Then make a query (again with javascript) to domain1.com with that token so that domain1.com can set a cookie in response. Now you are logged in at domain1.com with cookie and can continue.
Why we need all this at all, even if we have one application just reachable from different domains? Because browser does not know that and treats them completely different. In addition to that - http protocol is stateless and every request is not related to any other, so our server also needs confirmation that request A and B made by the same user, hence those tokens.
Yes, HttpServerUtility.UrlTokenEncode is perfectly fine to use here, even better than just Convert.ToBase64String, because you need to url encode it anyway (you pass it in query string). But if you will not pass token in query string (for example you would use javascript way above - you won't need to url encode it, so don't use HttpServerUtility.UrlTokenEncode in that case.
You are right on how cookie works, but that it not how OWIN works.
Don't override the cookie domain of the Auth Server(auth.domain.com).
You may override the cookie domain of the individual sites to "site1.domain.com" and "site2.domain.com".
In your SSO page, let's say someone lands on site1.domain.com and since is unauthenticated is taken to your auth server. The auth server takes the login credentials and sends a code to site1.domain.com on the registered URI(eg: /oauthcallback). This endpoint on site1.domain.com will get an access token from the code and SignIn(automatically write the cookie). So 2 cookies are written one on auth.domain.com and second on site1.domain.com
Now, same user visits site2.domain.com and finds a cookie of logged in user on "auth.domain.com". This means that the user is logged in and a new cookie is created with same claims on "site2.domain.com"
User is now logged into both site.
You don't manually write the cookie. Use OwinContext.Signin and the cookie will be saved / created.
To answer the question on your update, there is no way of sharing cookies across different domains.
You could possibly use some query strings parameters and some server side logic to handle this particular case, but this could raise some security concerns.
Se this suggestion: https://stackoverflow.com/a/315141/4567456
Update
Following your comment, here are the details:
https://blog.stackoverflow.com/2010/09/global-network-auto-login/
https://meta.stackexchange.com/questions/64260/how-does-sos-new-auto-login-feature-work
http://openid.net/specs/openid-connect-session-1_0.html
Bonus:
The mechanism in use today is a bit different, and simpler, than what is discribed in the first two links above.
If you look at the network requests when you login on StackOverflow, you will see that it logs you in individually to each site on the network.
https://stackexchange.com/users/login/universal.gif?authToken=....
https://serverfault.com/users/login/universal.gif?authToken=...
https://askubuntu.com/users/login/universal.gif?authToken=...
etc, etc...
William,
I understand that cookies are tied to a single domain.
Yes and there is no way you can manipulate it on the client side. The browsers never send a cookie of one domain to another.
But what is the easiest way of persisting a login across multiple domains?
External Identity Provider or Security Token Service (STS) is the easiest way to achieve this. In this setup all the domains site1.com. site2.com etc will trust the STS as the identity provider. In this federated solution, the user authenticates with the STS and the federated identity is used across all the domains. Here is a great resource on this topic from an expert.
Is there a way for me to read a cookie from a different domain, decrypt it, and recreate a new cookie for the differentdomain.com?
With some tweaks you may achieve this federated solution with your current setup. FYI, this is not recommended or an in-use approach, but an idea to help you achieve the goal.
Lets say you have multiple domains 1, 2, 3 pointing to a single application. I will create another domain STS pointing to the same application but deals only with cookie creation and validation. Create a custom middleware that utilizes the asp.net cookie authentication middleware under the wrap. This gets executed only if the requests are for STS domain. This can be achieved with a simple if condition on the domain/ host or by using the Map on IAppBuilder interface.
Lets look at the flow:
a. The user tries to access a protected resource using domain 1
b. Since he is not authenticated, he will be redirected to domain STS, with a query parameter for domain1 (for STS to identify which domain he is accessing the resource from) and the url for the protected resource on domain1
c. As the request is for STS domain, the custom middleware kicks in and authenticates the user. And sends two cookies one for STS and the second one for whatever the domain (in this case 1) he is trying.
d. Now the user will be redirected to the protected resource on domain1
e. If he tries to access protected resource on domain 2, he is not autheticated hence will be redirected to STS.
f. Since he had an authentication cookie for STS that will be attached with this request to STS by the browser. The STS middleware can validate the cookie and can authenticate the user. If authenticate, issues another cookie for domain 2 and redirects him to the protected resource on domain2.
If you closely look at the flow it is similar to what we do when we have an external STS, but in our case the STS is our application. I hope this makes sense.
If I had to do this task, I would use an external STS sitting on the same host (IIS). IdentityServer, an opensource implementation of OpenID Connect standard, is what I would use as STS. It is extremely flexible in terms of usage and can be co-hosted with our application (which I think is great deal in your case). Here are links Identity server, Video
I hope that this is helpful. Please let me know if you have any questions.
Thank you,
Soma.
It's easy to create an ASP.NET MVC application that authenticates based on windows domain user. It's also easy to create one that uses individual accounts stored using Entity Framework. In fact, there are project templates for both.
But I want to utilize BOTH kinds of authentication in the same application. I tried to combine the code from both project templates. I have a problem in Startup.Auth.cs.
// from "Individual Accounts" template
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
}
});
The existence of cookie authentication owin middleware seems to cause domain identities to become un-authenticated. If I take this line out, the domain authentication works. But without it, I can't seem to support individual user accounts.
I've downloaded the katana project source code and examined CookieAuthenticationHandler.cs, but I don't quite understand how it works in the context of an OWIN pipeline.
How can I use the ASP.net identity framework to allow my application to authenticate users from the windows domain OR an application-specific user store?
The simplest approach is to have 2 different presentation Projects only for Authentication/Authorization.
This has the advantage of leaning on existing framework and standard configuration.
From there, you decide to either
create an AD user for every internet user, or
create a DB/Internet user for every AD user.
Creating an Identity user for each AD user is easier to implement further. Then the same cookies and filters can exist in the entire app.
In that case you can either
use subdomain(s) for your app
AD Authentiction Project can have the singular purpose of Authentication / Authorization, then the Web App can represent the rest of your app.
Alternatively, If you want a truly Unified Solution, use MohammadYounes/Owin-MixedAuth
MohammadYounes/Owin-MixedAuth
Install-Package OWIN-MixedAuth
In Web.config
<location path="MixedAuth">
<system.webServer>
<security>
<authentication>
<windowsAuthentication enabled="true" />
</authentication>
</security>
</system.webServer>
</location>
In in Startup.Auth.cs
app.UseMixedAuth(cookieOptions);
:
:
How it works:
The handler uses ApplyResponseChallengeAsync to confirm the request is a 401 challenge. If so, it redirects to the callback path to request authentication from IIS which is configured to query the AD.
AuthenticationResponseChallenge challenge = Helper.LookupChallenge(
Options.AuthenticationType, Options.AuthenticationMode);
A 401 challenge is caused by an unauthorized users attempting to use a resource that requires Authentication
The handler uses InvokeAsync to check if a request is coming from a callback path (IIS) and then calls AuthenticateCoreAsync
protected async override System.Threading.Tasks.Task<AuthenticationTicket>
AuthenticateCoreAsync()
{
AuthenticationProperties properties = UnpackStateParameter(Request.Query);
if (properties != null)
{
var logonUserIdentity = Options.Provider.GetLogonUserIdentity(Context);
if (logonUserIdentity.AuthenticationType != Options.CookieOptions.AuthenticationType
&& logonUserIdentity.IsAuthenticated)
{
AddCookieBackIfExists();
ClaimsIdentity claimsIdentity = new ClaimsIdentity(
logonUserIdentity.Claims, Options.SignInAsAuthenticationType);
// ExternalLoginInfo GetExternalLoginInfo(AuthenticateResult result)
claimsIdentity.AddClaim(new Claim(ClaimTypes.NameIdentifier,
logonUserIdentity.User.Value, null, Options.AuthenticationType));
//could grab email from AD and add it to the claims list.
var ticket = new AuthenticationTicket(claimsIdentity, properties);
var context = new MixedAuthAuthenticatedContext(
Context,
claimsIdentity,
properties,
Options.AccessTokenFormat.Protect(ticket));
await Options.Provider.Authenticated(context);
return ticket;
}
}
return new AuthenticationTicket(null, properties);
}
AuthenticateCoreAsync uses AddCookieBackIfExists to read the claims cookie created by AD and creates it's own Claims based.
AD users are provided a Claims based Cookie identical to Web Users. AD is now like any other 3rd party authenticator (Google, FB, LinkedIN)
It's for this reason that I haven't been able to use pre-baked solutions for authentication. In our project, the passing years (and agile approach) have left us with 4 different ways to authenticate which is annoying, but we support all legacy versions of apps in the field so we have to preserve it all (at least for now).
I ended up creating a factory that figures out the authentication mechanism (through any of several means such as token format, presence of some other thing) and then returns a wrapper that carries the logic for validating that authentication method and setting the principal.
This gets kicked off in a custom HTTP module so that the principal is built and authenticated before the request gets to the controller. In your case, windows Auth would be the final fallback, I think. In our Web API application, we took the same approach but through a delegating handler instead of HTTP module. It's a type of local token federation, you could say. The current implementation allows us to add or modify any validation procedure, or add any other token format; in the end, the user ends up with a proper identity or gets denied. Only took a few days to implement.
It seems to me the best answer to this question is to use an authentication and authorization framework. There are plenty to choose from (both commercial and free). You could, of course, write your own but I would discourage it. Lots of very smart people get this wrong.
I would take a look at IdentityServer3. It's certainly not the only solution but its a pretty good authentication and authorization framework. It's open source and pretty easy to get up and running in a hurry. Your use case is a common one and you will find some very useful information at the link above. Clean separation between authorization and authentication, social authentication options, easy to work with json web tokens that encapsulate user claims, etc.
How it can help you
IdentityServer3 allows you to configure Identity Providers to handle authentication and there are plenty of extension points that will allow you to implement a chain of responsibility that can handle both of your scenarios. From the docs:
IdentityServer supports authentication using external identity providers. The external authentication mechanism must be encapsulated in a Katana authentication middleware.
Katana itself ships with middleware for Google, Facebook, Twitter, Microsoft Accounts, WS-Federation and OpenID Connect - but there are also community developed middlewares (including Yahoo, LinkedIn, and SAML2p).
To configure the middleware for the external providers, add a method to your project that accepts an IAppBuilder and a string as parameters.
IdentityServer3 supports AD as an identity providor via a browser login window and will support a more programmatic implementation via a custom grant. You can also take a look here for some more information on IdentityServer3 and AD authentication.
It will support windows authentication as well and you can take a look at here for information and examples on implementing that.
There is a pretty good getting started example here as well.
With the right configuration IdentityServer3 can handle your requirements. You will need to implement your own authentication providers and plug them into the framework but there isn't much more to it than that. As for authorization goes, there are plenty of options there as well.
An existing web application I want to migrate to the Windows Azure Cloud authenticates users the following way somewhere in the (post)authenticaterequest event:
IPrincipal current = Thread.CurrentPrincipal;
if (current != null && ((IClaimsIdentity)current.Identity).Claims.Count > 0)
{
IPrincipal result = AuthManager.CreateGenericPrincipal(current.Identity);
HttpContext.Current.User = result;
Thread.CurrentPrincipal = result;
}
The CreateGenericPrincipal method looks up roles in a xml file for the claimsidentity and creates a new GenericPrincipal with that roles.
Pages that need authentication just perform
IPrincipal p = Thread.CurrentPrincipal;
p.IsInRole("rolesFromXml");
This works fine with one webrole instance since there is no big difference to normal IIS hosting. But will it still work with 2, 3 oder 5 instances? The Azure loadbalancer is not "sticky", users could be forwarded to another instance while using the application. Dunno if Thread.CurrentPrincipal is still the way to go.
I use claims-based identity here. The first time an user enters the page, he gets forwarded to a security token service. Until now, this only happens once. It would be annoying if that happens several times when using multiple instances..
Thanks!
What typically happens is that you are forwarded only once, the redirect dance (passive redirect) happens, and you get a token. The token is typically cached in a cookie in an encrypted format. So, subsequent requests do not do the redirect dance.
The challenge here is that since the cookie is encrypted, all servers in a web farm must have the encryption key to decrypt. Out of box, you will run into issues with WIF because it defaults to DPAPI. This type of encryption is intentionally different per machine. That breaks in the cloud.
What you need to do is upload a service certificate as part of your deployment and change the way the cookie encrypted to something that is webfarm friendly. Here is the magical code:
private void OnServiceConfigurationCreated(object sender,
ServiceConfigurationCreatedEventArgs e)
{
var sessionTransforms =
new List<CookieTransform>(
new CookieTransform[]
{
new DeflateCookieTransform(),
new RsaEncryptionCookieTransform(
e.ServiceConfiguration.ServiceCertificate),
new RsaSignatureCookieTransform(
e.ServiceConfiguration.ServiceCertificate)
});
var sessionHandler = new
SessionSecurityTokenHandler(sessionTransforms.AsReadOnly());
e.ServiceConfiguration.SecurityTokenHandlers.AddOrReplace(
sessionHandler);
}
This sets up your security token handler to use RSA Encryption with key material derived from the installed certificate.
There is more detail and information outlined here in this sample application that illustrates the problem and solution:
http://msdn.microsoft.com/en-us/library/ff966481.aspx
Additional Edit:
There is a pipeline in ASP.NET where WIF is configured. It hooks the authentication event and will pull the token from the cookie and build your IPrincipal so that subsequent code will have that in the context. You typically don't build the Principal yourself when using an STS. Instead, if you need to modify the Principal, you plugin to the pipeline in WIF and insert additional claims to the 'role' claim (actually a URI namespace). WIF will then use those claims to build the ClaimsPrincipal that will contain things like Roles and things just work (IsInRole, web.config auth, etc.).
If possible, it is best to have the token contain the roles as claims. This is a much longer discussion however around 'normalization' of claims to meaningful contexts. Remember, the claims you get from a IP-STS is in their own terms and they might not mean anything to your application. For example, I might get a claim from a customer that they are part of Adatum\Managers group. That is completely meaningless to my application, so what I would typically do is exchange that token for one that my app understands and in the process transform or normalize the claims by claim mappings (i.e. Adatum\Managers --> MyApplicationAdminRole). Windows Azure ACS service is very applicable here to help do that (normalize claims from different IPs).
I would recommend reading Vittorio's book on this all to get the common patterns here:
Eugenio's notes:
Adding to what #dunnry wrote, which is all correct. The proper extensibility point to augment your claim set in the Relying Party (your web app) is by using a ClaimsAuthenticationManager. The docs for this type are here. there are pointers to samples in that page. In that class you would read the roles from the XML file and add them to the ClaimsIdentity. The rest of the app would not worry about claims, etc. (especially if you are using roles like in your case). The RSA config for the cookies encryption solves the load balancer issue.
Look at my post, I just did the same thing.
http://therubblecoder.wordpress.com/2011/10/25/wif-and-load-balancing-with-mvc-3/
Basically the claims token needs to be available to any cluster node, so using a certificate on the sessiontokenhandler will prevent a specific node processing the token in a manner specific to an instance.
In the microsoft.identity element in the config, you need to have an element that looks like this.
<serviceCertificate>
<certificateReference x509FindType="FindByThumbprint" findValue="****THUMBPRINT*****" storeLocation="LocalMachine" storeName="My" />
</serviceCertificate>
The application pool will also need to get access to this otherwise it won't be able to find the certificate by thumbprint.
The above code will use this certicate when dealing with the token. If you don't have this setup you will get a null reference exception.