We've are working on two (new) ASP.NET applications that use MVC/Razor views as a template engine to handle I18N/L10N (current culture is stored in, and retrieved from, a cookie on each request; Texts are loaded from resources).
Both applications consume the same WCF services through separate WebApis hosted in the same ASP.NET application as the MVC app. The APIs and are accessed by the web-based applications (HTML/JS).
Application A currently uses forms authentication (user credentials are validated by a WCF service against our application database), Application B uses windows authentication.
TL;DR abstract: We need to know stuff about the web-user down in the WCF services.
We need to:
- Have access to the current (web) user identity in the WCF services
- Transport verifiable information about that identity (e.g. role, group membership, ...) and use that information to restrict queries and operations (i.e. only allow to view and edit data in your own groups).
- Transparently authenticate users in application B (we do not want a separate login-screen or popup in IE)
- Support login through one time pads where authentication happened somewhat out-of-band (e.g. user gets an access-code which is only valid briefly, which authenticates him/herself as if he used his credentials to log in)
- Have brief session/token lifetimes, but use sliding expiration - user activity should extend his session/token lifetime. Inactive users should be logged out after short periods of inactivity.
- Have message security when communicating with the WCF services (ideally with certificates without having the actual authentication based on certificates due to the need for impersonation/delegation?)
My thoughts and impressions on this so far have been:
- Claims-based authorization would be nice to decouple the authorization from the business logic
- Claims-based authentication/authorization seems like it can transport all the information that we need to restrict the available data and operations (i.e. read group membership from claims)
- Token-based authentication is claims-based authentication (token is how claims are being transported)
- I need a security token service to authenticate the users (IdentityServer)
- Tokens could be somehow used during WCF channel creation to transport the current (claims) principal or corresponding token.
- I would have to use resource owner password credentials flow for our web-clients when migrating from forms to token-based authentication
So far I have:
- Set up IdentityServer with a single client using ROPC flow
- added OWIN to web application A and used
app.UseCookieAuthenticationandapp.UseIdentityServerBearerTokenAuthenticationto set up authentication but I'm not quite sure what exactly that entails so I'm not too comfortable with that for now. - On Login (via MVC) I request an (access?) token from IdentityServer via
TokenClient.RequestResourceOwnerPasswordAsync(username, password, scopes), try to build aClaimsIdentityby accessing the userinfo? endpoint and set the cookie viaRequest.GetOwinContext().Authentication.SignIn(id);. - A basic understanding on how token-based authentication and claims-based authorization works
What I don't have:
- Confidence that I fully understand how claim-based authorization works and can be implemented in WCF, MVC and WebApi
- Any idea what the different OWIN middlewares and extensions (
UseIdentityServerBearerTokenAuthentication,UseOpenIdConnectAuthenticationetc.) actually do, how and when they are supposed to be used and what security implications that has. - Any idea what the
wS2007FederationHttpBindingdoes and what it is used for, or what I have to configure inStartup.csand what needs to be configured in theweb.configor other places - How to secure everything with proper certificates, SSL/HTTPS in such a way that I can both use it during development in our team as well as for deployment (we have config transformations in place per developer as well as for each build configuration)
Basically, I've read so much about WCF, WIF etc. that I have no clear idea what information is current, what the best practices would be and how I could implement something that would fit all our requirements. The examples from IdentityServer didn't really help me much because, to me, they don't explain anything.
Any help with this would be greatly appreciated, even if you just help me get a better understanding of the terminology and technologies.
P.S.: We're running .NET 4.6
Update:
I've managed to get retrieve the token from IdentityServer and query the userinfo endpoint for the claims. Turns out I used ClaimTypes.Email etc. instead of Constants.ClaimTypes.Email - also only identity-scopes will yield claims on the userinfo endpoint, I had them specified on my resource scope.
My next step is trying to figure out how UseIdentityServerBearerTokenAuthentication prevents me from accessing anything and why it doesn't if I remove any required scopes...
Update: (2015-12-01) I'm currently trying to somehow transfer the token to the WCF services transparently and have the current principal (or claims principal) available depending on the token. I don't have a clear way of achieving this yet.
The MVC and WebApi stuff seems to work fine now:
The MVC application is secured with the OWIN cookie middleware - on login where the forms authentication was used, I use a TokenClient and resource owner password credentials flow. I then query the userinfo (via UserInfoClient) and introspection (via IntrospectionClient) endpoints to build the claims, create a ClaimsIdentity and save that in the cookie. I also save the access token as a claim.
var id = new ClaimsIdentity(listOfClaims, "Cookies");
this.Request.GetOwinContext().Authentication.SignIn(id);
Pitfall: the introspection endpoint requires to authenticate as the scope, not a client - which is slightly misleading in the samples, where the client and scope have the same name
To validate the token on each request, I configure the Provider of the CookieAuthenticationOptions as a new CookieAuthenticationProvider() where I query the introspection-endpoint of IdentityServer during OnValidateIdentity:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Cookies",
LoginPath = new PathString("/User/Login"),
LogoutPath = new PathString("/User/Logoff"),
// setup more options etc..
Provider = new CookieAuthenticationProvider()
{
OnValidateIdentity = async context =>
{
// validate etc. - and if it fails call: context.RejectIdentity();
}
}
}
The WebApi is detached from the cookie authentication by extending the Register(HttpConfiguration config) which was used to set up the WebApi:
// prevent use of owin cookie auth
config.SuppressDefaultHostAuthentication();
// need bearer token auth
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
Additionally, I've set up IdentityServerBearerTokenAuthentication in the owin startup (basically, this is the only thing I do with the WebApi because I have several routes to several Apis and had trouble setting up multiple maps with dependency injection via Ninject...).
app.Map(
"/api",
inner =>
{
inner.UseIdentityServerBearerTokenAuthentication(identityServerBearerTokenAuthenticationOptions);
});
Of course, that alone would only mean that the angular application would not be able to fetch any data - so on the single page application view I inject the token into the configuration as a constant and set the default headers by configuring the $httpProvider:
View:
<script type="text/javascript">
angular.module("myApp").constant("authorizationHeader", "Bearer @Model");
</script>
app:
myApp.config(["$httpProvider", "authorizationHeader", function ($httpProvider, authorizationHeader) {
$httpProvider.defaults.headers.common["Authorization"] = authorizationHeader;
}]);
...so my only problem right now is pretty much how to transfer the token to the WCF service and how to set the claims identity from that token on the service (without having to do that explicitly - i.e. I don't want a separate parameter on my contracts)
P.S.: I can only configure the WCF service in the app.config because we're using self hosted WCF services and Ninject...
Update 2015-12-10 Succcess!
I transmit the reference token to the WCF service similar to how Dominick Baier suggests doing it: http://leastprivilege.com/2015/07/02/give-your-wcf-security-architecture-a-makeover-with-identityserver3/
Essentially this boils down to:
- Wrapping the reference token in a
Claimin a newClaimsIdentity - Creating a new
SecurityTokenDescriptorfor that identity - Wrapping with a
Saml2SecurityTokenHandler - Wrapping in a
GenericXmlSecurityToken - Creating the channel via
this.ChannelFactory.CreateChannelWithIssuedToken(xmlToken);(hint: cache theChannelFactorybut not theChannel)
This means that I had to change the bindings to ws2007FederationHttpBinding, change all urls to use https and ports in the 44300 to 44399 range for development on localhost (because Windows has preconfigured certificates and access control list for those ports - for deployment you have to add your certificate and allow the WCF-host-user to host an HTTP/SSL service there).
If any metadata endpoints are visible, then those must use HTTPS too (binding="mexHttpsBinding" and <serviceMetadata httpGetEnabled="false" />)
In order to use that binding, the security has to be TransportWithMessageCredentials.
On the WCF-side, I removed all SecurityTokenHandler and added my own, which unwraps the reference token and uses the introspection endpoint from IdentityServer to build the identity (very similar to the example from IdentityServer).
That can be done in code or in the app.config (ServiceCommunication is the assembly where I keep all this stuff):
<configSections>
<section name="system.identityModel" type="System.IdentityModel.Configuration.SystemIdentityModelSection, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />
</configSections>
<system.identityModel>
<identityConfiguration name="identity">
<securityTokenHandlers>
<clear />
<add type="ServiceCommunication.Authentication.IdentityServerWrappedReferenceTokenHandler, ServiceCommunication" />
</securityTokenHandlers>
<claimsAuthorizationManager type="ServiceCommunication.Authorization.ClaimsAuthorizationManager, ServiceCommunication"/>
</identityConfiguration>
For Authorization I define my own ClaimsAuthorizationManager which handles everything in the overridden CheckAccess method. Beware: that method gets called for each WCF contract method too.
The authorization can be on any plain code via ClaimsPrincipalPermissionAttribute or explicit via ClaimsPrincipalPermission. Doesn't even have to be WCF. Works out of the box in .NET 4.5
I use the ClaimsPrincipalPermissionAttribute as deep down as possible in my WCF services and additionally on the MVC/API controller actions.
Now I've got authentication of all users, prevent access to non-authenticated users (with redirect to login) and guarantee that only authorized users can access specific methods. :)