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 [1] 
 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:

public static IEnumerable<MessageBoard> GetBoardsForUser(
  AuthorizationContext context, bool includePublic)
{
    List<MessageBoard> boards = new List<MessageBoard>();

    foreach (Claim typeClaim in context.ClaimSets.FindClaims(
      AppClaims.UserTypeClaim,
      new ApplicationIssuerClaimSet()))
    {
        string type = typeClaim.Get<string>();
        if ("Public".Equals(type) && includePublic == false) continue;

        boards.AddRange(GetBoards(type));
    }

    return boards;
}

I have updated the source download here to include a console, WCF and ASP.NET test app that share the same authorization model.


ASP.NET | CardSpace | IdentityModel | WCF
3/22/2008 1:54:39 PM UTC  #  Comments [3] 
 Friday, March 21, 2008

LINQ to SQL and Security

I don't know how many SQL injection demos I did in my life - and it is still surprising (or shocking rather) how many people don't know about this. It is even more surprising how many people fight for their string concat ad hoc queries and come up with all kind of dodgy excuses why it makes sense to use them.

What is wrong with SQL parameters? ;)

Recently I played around with LINQ to SQL and think it is a compelling (and time saving) way to do database interactions. The thing I really like about LINQ to SQL is that it does the right thing by default (security wise) when it comes to parameters.

Consider this query:

var products = from p in context.products
               where p.description.StartsWith(_txtSearch.Text)
               select new
               {
                   p.description,
                   p.price,
                   p.stock
               };

This gets turned into the following SQL (for a search value of 'sony'):

exec sp_executesql N'SELECT [t0].[description], [t0].[price], [t0].[stock]
FROM [dbo].[products] AS [t0]
WHERE [t0].[description] LIKE @p0',N'@p0 varchar(5)',@p0='sony%'

A (malicious) search value with an appended or 1=1 where clause would look like this:

exec sp_executesql N'SELECT [t0].[description], [t0].[price], [t0].[stock]
FROM [dbo].[products] AS [t0]
WHERE [t0].[description] LIKE @p0',N'@p0 varchar(16)',@p0='sony'' or 1=1 --%'

Which will not yield any results.

So if you really like ad hoc SQL - but for some reason are too lazy to work with parameters, LINQ is a compelling alternative (again purely from a security view).

Usually the databases I design (and how I recommend it to my customers), don't allow direct table access. All access is done via stored procedures. This is where LINQ to SQL comes in really handy for me - basically as a nice Sproc to C# code generator. Calling a GetData stored procedure and subsequent binding to a grid looks like this:

protected void _btnSearch_Click(object sender, EventArgs e)
{
    using (ComputerstoreDataContext context = new ComputerstoreDataContext())
    {
        var products = context.GetProducts(_txtSearch.Text);

        _gridProducts.DataSource = products;
        _gridProducts.DataBind();
    }
}

Which will result in this SQL:

declare @p4 int
set @p4=0
exec sp_executesql N'EXEC @RETURN_VALUE = [dbo].[GetProducts] @Search = @p0',N'@p0 nvarchar(4),@RETURN_VALUE int output',@p0=N'sony',@RETURN_VALUE=@p4 output
select @p4

This works for me.

Disclaimer: There's quite some controversy about LINQ to SQL and I am not a database expert enough to say if LINQ is good or bad. They way I use it (for Sprocs only) it seems to be fine. Again from a security point of view it does the right thing by default. And that's what I mostly care about ;)


Work in Progress
3/21/2008 1:16:11 PM UTC  #  Comments [2] 
 Thursday, March 20, 2008

Using IdentityModel: Adding ASP.NET Support Part 2 (Claims Manager)

The last step for integrating claims into ASP.NET is to write a module that loads authorization policies, creates an AuthorizationContext and persists that on Context.User/Thread.CurrentPrincipal.

My module has this simple configuration section:

<claimsManager enabled="true"
               addAuthenticationClaims="true"
               roleClaimType="urn:leastprivilege/claims/customers/status">
  <authorizationPolicies>
    <policy type="LeastPrivilege.CustomerIdAuthorizationPolicy, App_Code" />
    <policy type="LeastPrivilege.CustomerAuthorizationPolicy, App_Code" />
  </authorizationPolicies>
</claimsManager>

If addAuthenticationClaims is set to true, the policy that transforms authentication details to claims (see my last post) will be loaded before all the external policies. The roleClaimType attribute specifies the claim type that should be used for the IsInRole implementation of IdentityPrincipal. The authorizationPolicies collection specifies the claims transformation policies that should run.

The module itself subscribes to PostAuthenticateRequest, loads the policies and populates Context.User/Thread.CurrentPrincipal with the IdentityPrincipal (which in turn wraps the AuthorizationContext).

public class ClaimsManagerModule : IHttpModule
{
    public void Dispose()
    { }

    public void Init(HttpApplication context)
    {
        context.PostAuthenticateRequest += OnPostAuthenticateRequest;
    }

    private void OnPostAuthenticateRequest(object sender, EventArgs e)
    {
        // this code makes only sense when the user is authenticated
        if (!(HttpContext.Current.Request.IsAuthenticated))
        {
            return;
        }

        // make sure the module is enabled
        if (!Configuration.Enabled)
        {
            return;
        }

        HttpApplication app = sender as HttpApplication;
        HttpContext context = app.Context;

        IList<IAuthorizationPolicy> policies = ClaimsManagerModule.Policies;

        if (policies.Count != 0)
        {
            AuthorizationContext authContext =
              CreateAuthorizationContext(policies);

            IdentityPrincipal ip = new IdentityPrincipal(
              context.User.Identity, authContext);

            context.User = Thread.CurrentPrincipal = ip;
        }
    }

    private static AuthorizationContext CreateAuthorizationContext(
      IList<IAuthorizationPolicy> policies)
    {
        AuthorizationContext authContext =
            AuthorizationContext.CreateDefaultAuthorizationContext(policies);

        return authContext;
    }

    public static IList<IAuthorizationPolicy> Policies
    {
        get
        {
            List<IAuthorizationPolicy> policies = new
              List<IAuthorizationPolicy>();

            if (Configuration.AddAuthenticationClaims)
            {
                policies.Add(new AspNetAuthenticationPolicy());
            }

            foreach (PolicyType policy in
              Configuration.AuthorizationPolicies)
            {
                policies.Add(CreatePolicy(policy.Type));
            }

            return policies;
        }
    }

    private static IAuthorizationPolicy CreatePolicy(string typename)
    {
        Type type = Type.GetType(typename, true);

        if (!typeof(IAuthorizationPolicy).IsAssignableFrom(type))
        {
            throw new ConfigurationErrorsException(
              "Policy does not implement IAuthorizationPolicy");
        }

        return (IAuthorizationPolicy)Activator.CreateInstance(type);

    }

    public static ClaimsManagerSection Configuration
    {
        get
        {
            return (ClaimsManagerSection)
              WebConfigurationManager.GetSection("claimsManager");
        }
    }
}

 

Don't consider this as production ready code. There are things missing like error handling and caching. But this should get you started. With the next post I will show some usage scenarios and will also update the source download.

HTH


ASP.NET | IdentityModel
3/20/2008 6:54:32 AM UTC  #  Comments [0] 
 Wednesday, March 19, 2008

Using IdentityModel: Adding ASP.NET Support Part 1 (Authentication based Claims)

Adding claims support to ASP.NET is a perfect candidate for an HTTP module. As a reminiscence to RoleManager, I called mine ClaimsManager. The job of the claims manager is this:

  • Creating claims based on the technical authentication details (Windows, Forms, client certificates etc.)
  • Invoking external claims transformation policies which then build the app specific claims based on the technical ones
  • Making the AuthorizationContext available to pages (via Thread.CurrentPrincipal/Context.User - see my previous post)

The first step is to write an IAuthorizationPolicy to map the ASP.NET authentication details to claims. This is done by inspecting Context.User.Identity and client certificates - if you use a custom identity, you would amend that code (check my previous posts about authorization policies):

// policy that adds ASP.NET authentication type specific claims
public class AspNetAuthenticationPolicy : IAuthorizationPolicy
{
    public bool Evaluate(
      EvaluationContext evaluationContext, ref object state)
    {
        HttpContext context = HttpContext.Current;
        List<ClaimSet> claimSets = new List<ClaimSet>();

        // Windows or Forms authentication
        if (context.User.Identity is WindowsIdentity)
        {
            claimSets.Add(
              new WindowsClaimSet(context.User.Identity as WindowsIdentity));
        }
        else if (context.User.Identity is FormsIdentity ||
                 context.User.Identity is GenericIdentity)
        {
            claimSets.Add(new UserNameClaimSet(context.User.Identity.Name))
        }

        // client certificate
        if (context.Request.ClientCertificate.IsPresent)
        {
            X509Certificate2 certificate = new X509Certificate2(
                context.Request.ClientCertificate.Certificate);

            claimSets.Add(new X509CertificateClaimSet(certificate));
        }

        claimSets.ForEach(set => evaluationContext.AddClaimSet(this, set));
        return true;
    }

    public System.IdentityModel.Claims.ClaimSet Issuer
    {
        get { return