urbanhusky
urbanhusky

Reputation: 1385

ASP.NET MVC+API and WCF with claims-based authorization and authentication against a custom DB and active directory

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:

My thoughts and impressions on this so far have been:

So far I have:

What I don't have:

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:

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. :)

Upvotes: 2

Views: 2453

Answers (2)

urbanhusky
urbanhusky

Reputation: 1385

So I've finally solved all this:

  • I use IdentityServer3 for authentication and management of all the claims
  • app.UseCookieAuthentication with my own CookieAuthenticationProvider to verify the reference token in MVC and to handle login like the previous forms authentication
  • app.UseIdentityServerBearerTokenAuthentication for the API (each http request must have set a header Authorize with value Bearer your-token-here, most JS frameworks allow you to set that globally). Authentication is against IdentityServer, I've configured ClientId and ClientSecret (which are actually the ScopeId and ScopeSecret) so that it uses the introspection-endpoint. Beware: you'll only get claims for that specific scope, not for other scopes.
  • Various IdentityModel clients to authenticate and validate (and fetch claims from userinfo and introspection endpoints)
  • Wrapped reference or jwt token via Ws2007FederationHttpBinding and TransportWithMessageCredentials security
  • My own SecurityTokenHandler on the WCF services to unwrap that token and validate it (and set identity/principal)
  • My own ClaimsAuthorizationManager to authorize via ClaimsPrincipalPermission(Attribute)

Upvotes: 1

MvdD
MvdD

Reputation: 23436

New internet security protocols like OAuth2 and OpenID Connect, don't play well with WCF SOAP services.

The authorization server issues JWT tokens when using the OAuth2 and OpenID Connect protocols. However, your SOAP WCF service(s) expect a SAML token in the envelope header when you use the WS2007FederationHttpBinding. IndentityServer does not issue SAML tokens.

While it's possible to issue and sign your own SAML tokens using the claims from the JWT token, it may be easier to replace the WCF SOAP services with a REST based services that expects a JWT token in the Authorization header.

I'm not sure why you'd want to use the resource owner password credentials flow. You may want to look into using IdentityServer with an existing database and use the auth code flow instead.

P.S. Both IdentityServer and Katana (Microsoft's OWIN implementation) are open source, so you can figure out what all this code is actually doing.

Upvotes: 0

Related Questions