This post shows some of the implementation techniques for adding token and claims
based security to HTTP/REST services written with WCF. For the theoretical background,
see my previous post.
Disclaimer
The framework I am using/building here is not the only possible approach to tackle
the problem. Based on customer feedback and requirements the code has gone through
several iterations to a point where we think it is ready to handle most of the situations.
Goals and requirements
The framework should be able to handle typical scenarios like username/password based
authentication, as well as token based authentication
The framework should allow adding new supported token types
Should work with WCF web programming model either self-host or IIS hosted
Service code can rely on an IClaimsPrincipal on Thread.CurrentPrincipal that
describes the client using claims-based identity
Implementation overview
In WCF the main extensibility point for this kind of security work is the ServiceAuthorizationManager.
It gets invoked early enough in the pipeline, has access to the HTTP protocol details
of the incoming request and can set Thread.CurrentPrincipal. The job of the SAM is
simple:
Check the Authorization header of the incoming HTTP request
Check if a “registered” token (more on that later) is present
If yes, validate the token using a security token handler, create the claims principal
(including claims transformation) and set Thread.CurrentPrincipal
If no, set an anonymous principal on Thread.CurrentPrincipal. By default,
anonymous principals are denied access – so the request ends here with a 401 (more
on that later).
To wire up the custom authorization manager you need a custom service host – which
in turn needs a custom service host factory. The full object model looks like this:
Token handling
A nice piece of existing WIF infrastructure are security token handlers. Their job
is to serialize a received security token into a CLR representation, validate the
token and turn the token into claims.
The way this works with WS-Security based services is that WIF passes the name/namespace
of the incoming token to WIF’s security token handler collection. This in turn finds
out which token handler can deal with the token and returns the right instances.
For HTTP based services we can do something very similar. The scheme on the Authorization
header gives the service a hint how to deal with an incoming token. So the only missing
link is a way to associate a token handler (or multiple token handlers) with a scheme
and we are (almost) done.
WIF already includes token handler for a variety of tokens like username/password
or SAML 1.1/2.0. The accompanying sample has a implementation for a Simple Web Token
(SWT) token handler, and as soon as JSON Web Token are ready, simply adding a corresponding
token handler will add support for this token type, too.
All supported schemes/token types are organized in a WebSecurityTokenHandlerCollectionManager and
passed into the host factory/host/authorization manager.
Adding support for basic authentication against a membership provider would e.g. look
like this (in global.asax):
var manager
= new WebSecurityTokenHandlerCollectionManager();
manager.AddBasicAuthenticationHandler((username, password) =>
Membership.ValidateUser(username,
password));
Adding support for Simple Web Tokens with a scheme of Bearer (the current
OAuth2 scheme) requires passing in a issuer, audience and signature verification key:
manager.AddSimpleWebTokenHandler(
"Bearer",
"http://identityserver.thinktecture.com/trust/initial",
"https://roadie/webservicesecurity/rest/",
"WFD7i8XRHsrUPEdwSisdHoHy08W3lM16Bk6SCT8ht6A=");
In some situations, SAML token may be used as well. The following configures SAML
support for a token coming from ADFS2:
var registry
= new ConfigurationBasedIssuerNameRegistry();
registry.AddTrustedIssuer(
"d1 c5 b1 25 97 d0 36 94 65 1c
e2 64 fe 48 06 01 35 f7 bd db", "ADFS"); var adfsConfig
= new SecurityTokenHandlerConfiguration();
adfsConfig.AudienceRestriction.AllowedAudienceUris.Add(
new Uri("https://roadie/webservicesecurity/rest/"));
adfsConfig.IssuerNameRegistry = registry;
adfsConfig.CertificateValidator = X509CertificateValidator.None; //
token decryption (read from config) adfsConfig.ServiceTokenResolver
=
IdentityModelConfiguration.ServiceConfiguration.CreateAggregateTokenResolver();
manager.AddSaml11SecurityTokenHandler("SAML",
adfsConfig);
Transformation
The custom authorization manager will also try to invoke a configured claims authentication
manager. This means that the standard WIF claims transformation logic can be used
here as well. And even better, can be also shared with e.g. a “surrounding” web application.
Error handling
A WCF error handler takes care of turning “access denied” faults into 401
status codes and a message inspector adds the registered authentication schemes to
the outgoing WWW-Authenticate header when a 401 occurs.
The next post will conclude with authorization as well as the source code download.
(Wanna learn more about federation, WIF, claims, tokens etc.? Click here.)