Wednesday, July 02, 2008

Using IdentityModel: Useful Extension Methods for Serializing Claim Sets

As a follow up to my last post - the following extension methods make it easy to manually serialize claim sets:

public static XElement Serialize(
  this ClaimSet set, IEnumerable<Type> knownTypes)
{
    DataContractSerializer dcs = new DataContractSerializer(
        set.GetType(),
        knownTypes,
        int.MaxValue,
        false,
        true,
        null);
   
    MemoryStream ms = new MemoryStream();
    dcs.WriteObject(ms, set);
    ms.Seek(0, SeekOrigin.Begin);
   
    return XElement.Load(new XmlTextReader(ms));
}

 

public static XElement Serialize(
  this IEnumerable<ClaimSet> claimSets, string rootName,
  string rootNamespace, IEnumerable<Type> knownTypes)
{
    XNamespace ns = XNamespace.Get(rootNamespace);

    return new XElement(ns + rootName,
                    from cs in claimSets
                    select cs.Serialize(knownTypes));
}


IdentityModel
7/2/2008 9:13:54 PM UTC  #  Comments [0] 

Re:MVP

Quoting Brian:

"Microsoft has decided I didn't cause too much trouble over the last 12 months so I get to continue being a {0} MVP. Thanks!", "Developer Security"


Misc
7/2/2008 8:53:58 PM UTC  #  Comments [1] 

Using IdentityModel: Serializing Claim Sets

Both Claim and ClaimSet are decorated with DataContract/DataMember attributes. This means they are made for serialization. And this makes sense - maybe you want to forward a claim set (server to server) or send a claim set from server to client (UI authorization).

But you will most likely run into problems when trying to serialize a claim set using the DataContractSerializer.

Known Types
DCS needs to 'know' all types that are involved in the serialization process. This involves every type in the inheritance chain down to ClaimSet (e.g. DefaultClaimSet or my DeferredLoadClaimSet) as well as all possible resource types. You either supply the known types via attributes/config (KnownType and ServiceKnownType).

Or you supply the types when newing up the DCS manually:

DataContractSerializer dcs = new DataContractSerializer(
    typeof(ClaimSet),
    new List<Type> { typeof(DefaultClaimSet), typeof(UIClaimResource) });

 

Circular References
Typical claim sets will have circular references - e.g. when the last issuer in the chain points to himself. DCS is not made for cyclic reference - but rather object trees (at least with the default settings). When you are trying to serialize objects with cyclic references you will get the following exception : "type contains cycles and cannot be serialized if reference tracking is disabled.". In WCF traces you will see something like "message not logged because its size exceeds configured quota".

When newing up a DCS you can opt for "preserving object references". This will create ID/IDREF pairs in the serialized XML and allows for type references and thus cycles. (Aaron has an explanation of how that works).

DataContractSerializer dcs = new DataContractSerializer(
    typeof(ClaimSet),
    new List<Type> { typeof(DefaultClaimSet), typeof(UIClaimResource) },
    int.MaxValue,
    true,
    true,  // preserveObjectReferences
    null);

 

This is fine when you can control the DCS parameters. But you can't easily do that in WCF. Sowmy has a sample on how to enable reference preserving in WCF. This will solve the problem!

[OperationContract]
[ReferencePreservingDataContractFormat]
ClaimSet GetClaims();

 

3.5 SP1 to the Rescue!?
Starting with 3.5 SP1 you can enable reference preserving on a DataContract like this:

[DataContract(Namespace = "...", IsReference = true)]
public abstract class DeferredLoadClaimSet : ClaimSet

 

But there are two problems with this approach:

  • You actually need access to the DataContract to change the attribute. In the claims case - you would need to change the framework's DefaultClaimSet or your own ClaimSet-derived class.
  • Every DataContract in the inheritance chain needs the IsReference attribute - otherwise you will get the following error: "Derived types must have the same value for IsReference as the base type". Since all custom claim sets ultimately derive from ClaimSet - but this DataContract has no IsReference set, we are back to square one.

 

Conclusion
Keep these things in mind when serializing claim sets:

  • supply all involved types as known types
  • Set preserveObjectReferences to true on the DCS. The new attribute on DataContract in 3.5 SP1 is nice - but does not help with claim sets. Use the [ReferencePreservingDataContractFormat] attribute instead (find the code here).
  • Reference preserving adds ID/IDREF attributes to the resulting XML. These attributes come from a Microsoft namespace. This may be a problem for interop scenarios. If you need full control over the XML, either use the DCS extensibility points for manual serialization, or don't use the DCS at all (and use one of the alternative message generation mechanisms). Another option would be to use a more standardized serialization format for claims like a SAML token.
  • WindowsClaimSet and X509CertificateClaimSet are not marked with [DataContract] at all - they are not intended for serialization.

HTH


IdentityModel
7/2/2008 8:00:28 AM UTC  #  Comments [0] 
 Sunday, June 15, 2008

PowerShell Profile

Putting these three things (and a little bit of this) together - you can build a very nice profile script for PowerShell ;)


Work in Progress
6/15/2008 2:44:38 PM UTC  #  Comments [0] 
 Thursday, June 12, 2008

Advanced Extensions to IIS 7 Configuration

Great article about IIS 7 configuration extensibility:

http://learn.iis.net/page.aspx/241/configuration-extensibility/

Especially infos about the COM backed extensions are hard to find elsewhere...


IIS
6/12/2008 10:55:59 PM UTC  #  Comments [0] 
 Saturday, June 07, 2008

Software Architect 2008

Thanks to everyone who attended my IdentityModel talk at Software Architect.

You can have all the code I showed you during my talk - just send me a private message or leave a comment. Most of the demos are online anyways - have a look at my IdentityModel micro-site.

Questions and feedback are more than welcome. Happy identity-ing.


Conferences | IdentityModel
6/7/2008 4:44:43 AM UTC  #  Comments [0] 
 Thursday, June 05, 2008
 Wednesday, May 28, 2008

SQL Server Security Best Practices

Bob wrote me an email as a response to this post. He also directed me to this whitepaper he wrote about SQL Server Security. Interesting read!


For Your Favourites
5/28/2008 8:39:37 AM UTC  #  Comments [0] 
 Monday, May 26, 2008

OpenID Phishing Demo

Funny and educational:

http://idtheft.fun.de/


For Your Favourites | IdentityModel
5/26/2008 8:02:32 PM UTC  #  Comments [1] 

System Accounts and SQL Server 2005

I recently ran into a strange situation - I was expecting an "access denied" but it didn't happen (yes - security guys are strange people ;). Here's the long story:

I was writing some test code for LINQ to SQL (see here) in ASP.NET. Since this was on a freshly installed box I was expecting an access denied since I hadn't created a SQL login for Network Service yet. But it worked - I could successfully query (and update) data in all databases. Shock.

After some investigation I found the reason for this behavior. Since I was using SQL Express, the SQL instance was running as Network Service (the default). Furthermore setup creates a Windows group for SQL Server service accounts (e.g. MACHINE\SQLServer2005MSSQLUser$...) and puts Network Service in there. It turns out that this Windows group is mapped to a SQL login with a server role of sysadmin...

This means (on my machine) that all SQL clients running as Network Service (or can get an impersonation token for that account) have sysadmin privileges in the SQL Server installation. Or more generally - when a client can use the same Windows account as SQL Server itself - it will get sysadmin privileges

I thought I might point this out, since running SQL Server and ASP.NET as Network Service seems to be a pretty common configuration.

The moral of the story: Always create dedicated service accounts for SQL Server (or every service you install).

btw - the full blown SQL Server installation specifically asks you for the account to use (but also gives Network Service as a choice).


Work in Progress
5/26/2008 11:26:37 AM UTC  #  Comments [0] 
 Friday, May 23, 2008

Avoid unhandled Exceptions in WCF Error Handlers

The IErrorHandler interface in WCF allows to write some central error handling code that gets invoked whenever an unhandled exception bubbles up from your service. There are two methods to implement:

  • ProvideFault - called on the request thread to turn the exception into a fault message
  • HandleError - called on a separate thread for error logging and the like

While WCF tries its best to shield the service host from all kinds of error conditions, there are some situations where unhandled exceptions can hurt your hosting process. One of them is the HandleError method on IErrorHandler.

HandleError is called on a background thread to allow doing (kind of) lenghty operations without impacting the request where the error originally occurred. If you have an unhandled exception in HandleError the normal CLR rules for excpetions in background threads apply - which means shutting down the process. Be careful here.


WCF
5/23/2008 8:26:30 AM UTC  #  Comments [1] 
 Sunday, May 18, 2008
 Thursday, May 15, 2008

Two important Security changes in .NET 3.5 SP1

Shawn details the two big security changes in .NET 3.5 SP1 on his blog:

We have discussed both changes internally - and I have mixed feelings about them. I guess the most important thing to be aware of is, that they are not opt-in changes. By installing SP1 - the behavior will change automatically - if you like it or not.


FX Security
5/15/2008 6:53:45 AM UTC  #  Comments [0] 
 Wednesday, May 14, 2008

Improved IisRegMgmt

Thanks to CarlosAg from the IIS team, I was able to improve my tool for registering IIS 7 management modules.

IisRegMgmt01.zip


IIS
5/14/2008 6:34:10 AM UTC  #  Comments [0] 

P2P and WCF: The PeerName Tool

To play around with peer name registration and resolution, I wrote a little tool that makes this easy (yes - I know all this functionality is also available via netsh - but I wanted something more specialized).

Registering

Resolving

PeerName.zip (27.31 KB)

 


WCF
5/14/2008 6:03:31 AM UTC  #  Comments [0] 
 Monday, May 12, 2008

Using IdentityModel: Tracing

While reading through some of the code of System.IdentityModel, I noticed that there is some diagnostics tracing going on. Just add a trace listener for the source 'System.IdentityModel' to your config file.

HTH


IdentityModel | WCF
5/12/2008 5:28:09 PM UTC  #  Comments [2] 
 Friday, May 02, 2008

P2P and WCF: Some Resources

If you want to know more about P2P and its related protocols and components, here's a list of online resource I found useful while researching:

Have fun!


For Your Favourites | WCF
5/2/2008 4:09:01 AM UTC  #  Comments [0] 
 Thursday, May 01, 2008

P2P and WCF: Exposing a Service

The last post explained how to find a PNRP registered service. What else do you have to do for e.g. exposing a WCF service over the P2P infrastructure?

Code-wise nothing. If the WCF service listens on all NICs (the default), a client can do a resolution via the peer DNS name and connect to it. Easy.

Well - hold on - does that mean that arbitrary clients can now traverse my NAT and connect to my intranet machine? Kind of - yes...

For the service to be accessible you also have to adjust firewall rules:

  • the port the service is listening on must be openend (this will allow normal TCP/IP traffic to the endpoint)
  • to allow Teredo traffic to the service, additionally the "allow edge traversal" option must be checked. This option is only available via the advanced firewall (available via Administrative Tools or MMC). See screenshot:

So to recap - these are the prereqs for a globally reachable service:

  • P2P (PNRP and Teredo) must work properly
  • the service must be registered
  • the client (or peer) must know the peer name
  • the endpoint port must be opened in the firewall
  • Teredo traffic must be allowed for this port

But one thing is very true, you now allow (internet) inbound traffic to an intranet hosted service, which has some implications:

  • there is probably no security around that intranet machine (like a DMZ).
  • intranet machines are typically not hardened for exposing internet services.
  • this means that if the service has some security problem (e.g. directory traversal etc), there are no safe-nets that will stop an attacker e.g. accessing other machines or system resources.
  • your administrators may not like this!

Typical P2P scenarios don't necessarily involve publicly known peer names, so you maybe only have a limited exposure. But still - the traffic bypasses perimeter security and goes directly into the intranet. So be careful.

 


WCF
5/1/2008 9:26:52 AM UTC  #  Comments [0] 

P2P and WCF: Finding a Service

After you have registered a service, the next step is to find it again. The System.Net.PeerToPeer API includes a PeerNameResolver class which does that.

You input the peer name and get back the registration details (IP addresses, port, comment and data):

static void Resolve(string name)
{
    PeerNameResolver resolver = new PeerNameResolver();
    PeerName peerName = new PeerName(name);

    Console.WriteLine("Resolving {0}...", peerName);
    Console.WriteLine();

    PeerNameRecordCollection results = resolver.Resolve(peerName);

    if (results.Count == 0)
    {
        Console.WriteLine("No records found.");
        return;
    }

    int count = 1;
    foreach (PeerNameRecord record in results)
    {
        Console.WriteLine("Record #{0}\n", count);
        Console.WriteLine("DNS Name: {0}", record.PeerName.PeerHostName);

        Console.WriteLine("Endpoints:");
        foreach (IPEndPoint endpoint in record.EndPointCollection)
        {
            Console.WriteLine("\t Endpoint:{0}", endpoint);
        }

        count++;
    }
}

The way you will resolve peer names more commonly is via the DNS format (the PeerHostName property in the above code). A peer name also has DNS name representation (e.g. foo.pnrp.net for an unsecured service named foo). Whenever you use this format (e.g. with ping or any other application that does DNS name resolution), Windows will use the P2P APIs internally to return the corresponding IP address). See here for the details.

This e.g. means that you could make a registration on a web server for port 80 and can use the browser to directly connect to the web server using the DNS format name.


WCF
5/1/2008 6:43:10 AM UTC  #  Comments [0] 

P2P and WCF: Registering a Service

To make a service discoverable using the P2P infrastructure, you first have to do a so called peer name registration.

A peer name registration has the following properties:

  • a name (there are two different flavours: secured and unsecured - more on that later)
  • one or more IP addresses and scope
  • a port number
  • a comment (optional)
  • up to 4KB of binary data (optional)

Peer Name
The name of the service you want to register. Names have the following format: 'authorityId.Name'. Unsecured Names use a '0' as the authorityId and are easy to spoof/squat. When using a secured name, a key/pair is generated on the fly (the first time only) to sign the registration request. The public key hash becomes the authorityId in this case.

IP addresses and scope
That's the most fascinating (and complicated) part. The peer name registration can have local and/or a global scope. A global scope means that the service can be discovered and contacted - well - globally. How can that work, given the service is behind a NAT device? IPv6 is the answer.

Now you may ask yourself: "but my network/router hardware is not IPv6 enabled, can this still work?". Yes it does - making the transition between IPv4 and IPv6 is the job of so called transition or tunneling protocols. Teredo is the name of the protocol that is typically used here. Teredo has several jobs - one is to provide a globally unique IPv6 address, the other is to enable NAT traversal. I won't go into the Teredo details here, but this document describes how it works.

When you do a 'ipconfig' on the command line you may already see a bunch of IPv6 addresses. The one that is directly associated with your NIC is the local address. You may also see a "Tunnel Adapter" interface - that would be the global Teredo provided address.

You can check the status/health of the Teredo protocol by using this command: 'netsh int teredo show state'. This article helps you with troubleshooting if Teredo should not be enabled on your machine.

You can also have a look at the scope of your registration by checking the clouds to which your machine has access. This is done by doing a 'netsh p2p pnrp cloud show list'. You should see one or more LinkLocal_ clouds and a Global_ cloud.

Again this article has all the details on clouds and their background.

So to wrap it up - by default a peer name registration will use all available NICs/IP addresses. If you have a global IPv6 address (which means that Teredo is working properly and you can 'see' the global cloud) this one is used also. This in turn means that the service can be used by every client that also has a global address.

The remaining properties are self explaining I think.

The following code snippet would register a secured peer name in all available clouds (you can find the APIs in the System.Net assembly (v3.5):

private void Register(string name, int port, string comment)
{
    PeerName peerName = new PeerName(name, PeerNameType.Secured);
    PeerNameRegistration reg = new PeerNameRegistration();
   
    reg.PeerName = peerName;
    reg.Port = port;
    reg.Cloud = Cloud.Available;
    reg.Comment = _cl.Comment;

    reg.Start();
}

The next posts will deal with peer name resolution and how to host a WCF service over this infrastructure.


WCF
5/1/2008 5:42:58 AM UTC  #  Comments [0] 
 Wednesday, April 30, 2008

P2P, PNRP, Teredo...the Motivation

Since I started playing around with computers, communication of machines over a "wire" has been fascinating to me. This is probably why I ended up in the distributed applications space.

Typically clients talk to servers and servers to servers - but less common clients directly to clients. But applications like MSN Messenger or Skype show useful use cases of client to client communication. With Vista and Server 2008 (and XP SP2 + some components) peer to peer networking has become part of the operating system. There is also a peer channel in WCF that sits on top of these core components. Time to have a closer look.

So what features is the P2P infrastructure supposed to give you?

  • Global/local registration of services
  • Global/local connectivity between peers which includes the capability to traverse NAT devices
  • Global/local virtual broadcasting networks
  • Peer/Service Discovery
  • Invitation/activation of P2P enabled applications

All of these capabilities are as fascinating as they are scary. More importantly I really think that P2P communication patterns will be a "big" thing and will also change the way we have to think about network/perimeter security.

In the next posts I will write about some of my findings. Stay tuned.


WCF
4/30/2008 6:18:56 AM UTC  #  Comments [0] 
 Monday, April 28, 2008

Using IdentityModel: Converting ADFS Security Properties to Claims

This little helper might be useful when you are working with ADFS, but want to use the IdentityModel types in your app:

public static ClaimSet ToClaimSet(this SingleSignOnIdentity identity)
{
    List<Claim> claims = new List<Claim>();

    claims.Add(new Claim(identity.NameType, identity.Name, Rights.Identity));

    foreach (SecurityProperty property in identity.SecurityPropertyCollection)
    {
        string claimType = property.Uri;
        if (claimType.EndsWith("NameValue"))
        {
            claimType = property.Name;
        }

        claims.Add(new Claim(claimType, property.Value, Rights.PossessProperty));
    }

    return new DefaultClaimSet(ClaimSet.System, claims);
}


IdentityModel
4/28/2008 5:39:21 AM UTC  #  Comments [0] 
 Tuesday, April 22, 2008

Ein Session Abstract ganz genau nach meinem Geschmack

Gesehen auf der JAX2008 Webseite:

Security Last – Sicherheitsentscheidungen spät treffen
Sicherheitsanforderungen wie Logins und Berechtigung sind wichtig – aber müssen diese wirklich gleich am Anfang umgesetzt werden? Das nachträgliche Hinzufügen dieser Anforderungen mit reinem Java und OOP ist sehr schwierig, weshalb dies meistens mit „Ja“ beantwortet wird. Erfahren Sie hier, wie mithilfe von Tools wie Spring Security, AspectJ und CAS auch spät in Anwendungen integriert werden kann.

Ohne Worte...

 


Microsoft Deutschland Security Portal
4/22/2008 11:49:20 AM UTC  #  Comments [1] 
 Sunday, April 20, 2008

Token Kidnapping

Interesting...and shocking.

Read more here:
http://www.argeniss.com/research/TokenKidnapping.pdf


For Your Favourites
4/20/2008 7:15:28 AM UTC  #  Comments [0] 

Installing an IIS 7 Extension

Related to cleaning up my authentication module for Codeplex, I needed a way to (semi) automatically install a complete IIS extension (including schema, config sections and management extensions). I came up with a batch file that does the necessary steps (anybody out there that wants to write a real installer?).

1. Register all assemblies in the GAC
Usually an IIS 7 extension consists of at least three assemblies (module/handler, server extensions, client extensions). Gacutil.exe is your friend here (use the /if option). Also take into account, that IIS loads GACed assemblies domain neutral. That means that you have to recycle the worker process when you update e.g. your GACed module.

2. Register schema and config section
This involves copying your schema to the IIS' schema directory and add a <configSection> registration to applicationHost.config. Mike Volodarsky from the IIS team has written a nice tool call IisSchema that automates this step.

3. Registering the management extension
This involves adding the module to administration.config (in two different places). I haven't found an automated way of doing this, so I wrote a little tool to accomplish this task.

IisRegMgmt [install/uninstall] [assembly_to_register]

This will find all Microsoft.Web.Management.Server.ConfigurationModuleProvider derived classes in the specified assembly and register them in administration.config.

The code to do the registration is as follows:

private static void RegisterAdministration(string name, string type, string assembly)
{
    // get access to administration.config
    Configuration administration = _manager.GetAdministrationConfiguration();

    // get access to <moduleProviders>
    ConfigurationSection moduleProvidersSection =
        administration.GetSection("moduleProviders");
    ConfigurationElementCollection moduleProviders =
        moduleProvidersSection.GetCollection();

    // check for existing elements first
    Clean(name, moduleProviders);

    // create new element
    ConfigurationElement newModuleProvider =
        moduleProviders.CreateElement();

    // set attributes
    newModuleProvider.SetAttributeValue("name", name);
    newModuleProvider.SetAttributeValue("type", type + ", " + assembly);

    // add element
    moduleProviders.Add(newModuleProvider);


    // get access to <modules>
    ConfigurationSection modulesSection =
        administration.GetSection("modules");
    ConfigurationElementCollection modules = modulesSection.GetCollection();

    // check for existing element first
    Clean(name, modules);

    // create new element
    ConfigurationElement newModule = modules.CreateElement();

    // set attributes
    newModule.SetAttributeValue("name", name);

    // add element
    modules.Add(newModule);

    // save changes
    _manager.CommitChanges();
}

Don't forget to clean up the sections before you add the new module, otherwise you might end up with double entries:

private static void Clean(string name, ConfigurationElementCollection elements)
{
    var hits = from e in elements
               where (string)e.GetAttributeValue("name") == name
               select e;

    hits.ToList().ForEach(e => elements.Remove(e));
}

For completeness sake, here's how you can find the right management classes in the assembly (and because I was amused about 'Linq to Reflection' ;)

private static IEnumerable<string> GetManagementTypes(Assembly a)
{
    string baseType = "Microsoft.Web.Management.Server.ConfigurationModuleProvider";
   
    var types = from t in a.GetExportedTypes()
                where t.BaseType.FullName == baseType
                select t.FullName;

    return types;
}

 

IisRegMgmt01.zip (18.66 KB)

 


IIS
4/20/2008 6:50:57 AM UTC  #  Comments [2] 
 Saturday, April 19, 2008

Custom Basic Authentication is now on Codeplex

I also moved my "Basic Authentication against non-Windows accounts for IIS" (phew) project to Codeplex

http://www.codeplex.com/CustomBasicAuth

Again, if you have feature ideas or want to contribute, contact me.

 


ASP.NET | IIS | WCF
4/19/2008 7:44:05 AM UTC  #  Comments [0] 
 Wednesday, April 16, 2008

InfoCardSelector is now on Codeplex

I finally found a new home for my ASP.NET InfoCard control:

http://www.codeplex.com/InfoCardSelector

If you feel like contributing or suggesting new features, you can contact me via this page.


ASP.NET | CardSpace | IdentityModel | WCF
4/16/2008 4:02:36 PM UTC  #  Comments [0] 
 Saturday, April 12, 2008

Developing ASP.NET Applications in Medium Trust

Alex Smolen did a good write up of the various approaches for sandboxing ASP.NET applications. Recommended!


ASP.NET
4/12/2008 5:56:02 AM UTC  #  Comments [0] 
 Thursday, April 03, 2008

Beware of Whitespaces in WAS Configuration

This took me a while to solve...

In the WAS configuration dialog there must be NO whitespaces in the enabled protocols configuration. Otherwise you will get this error:

"Could not find a base address that matches scheme net.tcp for the endpoint with binding NetTcpBinding."

 

HTH

 


IIS | WCF | Work in Progress
4/3/2008 8:04:24 AM UTC  #  Comments [0] 
 Sunday, March 30, 2008

Using Information Cards in ASMX Web Services

As I wrote here - an Information Card token is just a string. This means that (with the help of some extra plumbing) you can seamlessly integrate cards into "legacy" technologies. Here's a sample walkthrough for ASMX web services.

To transmit the token to the service, I will use a SOAP header. So the first step is to define the header:

[XmlRoot(ElementName = "InformationCard",
    Namespace = "http://schemas.xmlsoap.org/ws/2005/05/identity")]
public class InfoCardTokenHeader : SoapHeader
{
    public string Token;
}

For metadata support, we can now annotate a web method with this header information:

[WebService(Namespace = "urn:leastprivilege")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class AsmxService : WebService
{
    public InfoCardTokenHeader InfoCardToken;

    [WebMethod]
    [SoapHeader("InfoCardToken", Direction = SoapHeaderDirection.In)]
    public string Ping()
    {
      
    }
}

The client can now use the CardSpaceSelector API (or my wrapper) to get a token manually. Afterwards the token gets transmitted using the header:

static void Main(string[] args)
{
    AsmxService proxy = new AsmxService();

    InfoCardTokenHeader token = new InfoCardTokenHeader();
    token.Token = GetInfoCardToken(proxy.Url);
    proxy.InformationCard = token;

    Console.WriteLine(proxy.Ping());
}

static string GetInfoCardToken(string targetUri)
{
    IdentitySelector selector = new IdentitySelector();
    selector.TargetUri = new Uri(targetUri);
    selector.SetTargetCertificate(targetUri);

    selector.RequiredClaims.Add(ClaimTypes.GivenName);
    selector.RequiredClaims.Add(ClaimTypes.Surname);
    selector.RequiredClaims.Add(ClaimTypes.Email);

    GenericXmlSecurityToken token = selector.GetToken();
    return token.TokenXml.OuterXml;
}

On the server side you could now retrieve the token from the header and use your favourite token decryption class to extract the claims. If you want to put in a little bit more work, you can improve the integration of that information using a SoapExtension.

The extension will check the incoming headers, extract the token and set Thread.CurrentPrincipal and Context.User to an instance of IdentityPrincipal that wraps the token generated authorization context. A corresponding extension attribute connects this logic with the web method:

[WebMethod]
[InfoCardSoapExtension(TokenRequired = true)]   
[SoapHeader("InfoCardToken", Direction = SoapHeaderDirection.In)]
public string Ping()
{
    return IdentityPrincipal.Current.ClaimSets.FindClaim(
        ClaimTypes.GivenName).Get<string>();
}

This gives the web service method seamless access to incoming claims.

 

The code for the SOAP extension is quite simple (the configuration code is omitted):

public class InfoCardSoapExtension : SoapExtension
{
  public override void ProcessMessage(SoapMessage message)
  {
    if (message.Stage == SoapMessageStage.AfterDeserialize)
    {
      foreach (SoapHeader header in message.Headers)
      {
        InfoCardTokenHeader tokenHeader =
          header as InfoCardTokenHeader;

        if (tokenHeader != null)
        {
 
        IdentityPrincipal principal;

          try
          {
            var token = new Token(tokenHeader.Token, true);
            principal = new IdentityPrincipal(token.AuthorizationContext);
          }
          catch
          {
            throw new HttpException(500, "Token validation failed");
          }

          HttpContext.Current.User = Thread.CurrentPrincipal = principal;
          return;
        }
      }

      if (_tokenRequired)
      {
        throw new HttpException(401, "Authentication required");
      }
    }
  }
}

Disclaimer: I know that this code could be written far more generic. Consider this as a proof of concept.

 

The LeastPrivilege.IdentityModel download contains the complete sample. Have fun.


ASP.NET | CardSpace | IdentityModel
3/30/2008 8:49:07 AM UTC  #  Comments [5] 
 Saturday, March 22, 2008

Using IdentityModel: Some Samples

Here are some typical usage scenario of IdentityPrincipal in ASP.NET.

Simple IsInRole calls (checks for a status claim with a value of 'Gold'):

HttpContext.Current.User.IsInRole("Gold");

 

Retrieving the OrderHistory claim:

IdentityPrincipal ip = IdentityPrincipal.Current;
Claim orderHistory = ip.ClaimSets.FindClaim(
  Constants.OrderHistoryClaimType,
  Constants.ApplicationIssuerIdentityClaim);

var orders = orderHistory.Get<List<OrderDetail>>();

 

..or some authorization code from my CardSpace sample app - thanks to the unified authorization model, I can share this method across ASP.NET, ASMX and WCF: