Pages

Sunday, November 6, 2016

Catching Up on Coding - Claims-Based Identity Part 1

the-magician

I’ve been a software developer for twenty years, but am behind in some skills. This series chronicles my self-improvement.

Claims-based identity isn't hard to understand, but it's been challenging to learn and implement in a legacy environment. In part one, we'll look at the concepts. In future posts, I'll have code samples using current (at the moment) frameworks.

Like Tony Blake producing a flock of doves, we'll learn some security sleight-of-hand.

Previously on CUOC: TDD Part 3
Next Episode: Claims-Based Identity Part 2

 

 

 

Claims-Based Identity Basics

What We're Used To - Passwords

The convention in authentication for decades--for millennia, in a sense--has been this exchange:

"Let me in."
"What's the password?"
"Swordfish."

You're allowed in because you know something only a few people should: the password. Once you're in, the exchange continues.

"I want to see the boss."
"Oh yeah? Who's asking?"
"I carry packages for Don Crosetti."
"A courier? Delivery boys don't see the boss, chum."

What you get to do is defined by your role. A lieutenant can see the boss. A courier can't.

This is very much how people understand software security to work. You enter a username and password on a web site, let's say a Content Management System. Once in the site, if you're a Viewer, you can look at all the pages but not edit anything. An Blogger can create posts for her own blogs, but not anyone else's. A Site Editor can modify anyone's posts.

This system works well enough for many situations (in both software and life). But there's another common approach: claims.

What We're Also Used To - Claims

It's Friday night and you go out to a dance club. To get in, you have to show your driver's license. The guy at the door looks it over, then waves you in. What was he checking? Two things.

  • Were you over 21?
  • Did the license look valid for the issuing state?

The first item is a claim. You're claiming to be over twenty-one. You have a driver's license issued by a recognized identity validator: the Bureau of Motor Vehicles. The doorman didn't verify your identity. He didn't ask for a social security card or birth certificate or fingerprints. He trusts that the BMV did all that. The most he did was check that the license was the expected color and had the right hologram.

As long as he trusted the license, he trusted the claims on the license: your birth date, name, height, weight, etc.

Roles can be claims: maybe at this particular club you have a VIP card that lets you into the upper floor. However, claims are not necessarily roles. Claims can allow more fine-grained and nuanced authorization.

There are two important aspects of this example:

  1. The bar didn't ask for a password. They trusted a third party's identity authentication.
  2. The bar authorized entry based on a claim, not a role.

With those two real-world examples in mind, we're ready to look at claims-based identity in software.

What's an Identity Provider (Security Token Service)?

In the bar example, the identity provider is the BMV.

An STS takes care of authenticating identity, if needed, and returns a token than contains claims about the identity. An STS could be local or remote. Examples include:

  • A local database containing user and password hashes.
  • A local Active Directory
  • Azure Active Directory
  • Google

What's a Token?

In the bar example, the token is the driver's license.

A token is just data about the identity and issuer. It could be an XML document, for instance. Generally speaking, it can have:

  • A unique ID
  • An issuer certificate id
  • A collection of claims

What's a Claim?

In the bar example, the claim is the age.

A claim is a fact about the identity (i.e., the user). A claim is trusted if the identity provider is trusted. Claims could include:

  • Name
  • Email
  • Date of Birth
  • Country

What's Federated Identity?

The word "federation" means "union by agreement."

A body formed by a number of nations, states, societies, unions, etc., each retaining control of its own internal affairs. [Dictionary.com]

When it comes to identity, otherwise unrelated organizations can trust each other's STSs, allowing a user to log in with one set of credentials that are accepted elsewhere. This forms the basis of single sign-on (SSO).

A simple example of real-world federation would be a background check company that wants to give its customers access to their reports without storing them as users. The company would:

  1. Set up a federated STS.
  2. Establish a trust with the customer's Active Directory (for example).
  3. The customer determines which AD claims to provide.

A customer's user would log into their computer using their corporate credentials. When she opened the background check web site, the site would check if there was an authenticate token, and find the user's credentials in a cookie (perhaps). The web site would contact the STS, which would contact the customer's AD and verify the token's validity and return claims. The site would then authorize access if the claims allowed.

At no time did the user retype credentials.

Simplest Steps of Getting Authenticated Claims - Code Neutral

Here's some pseudo-code (C# style) showing how an application might get and use a claims token. Note: These are not real .Net classes!

A simple user.

class User
{
    public string Email { get; set; }
}

The claims class. (The real .Net class is just about this simple.)

class Claim
{
    public string Type { get; set; }
    public string Value { get; set; }
}

A token/identity class, which contains a collection of claims.

class IdentityToken
{
    public string TokenId { get; set; }
    public IEnumerable<Claim> Claims { get; set; }
}

A helper class that checks authentication status.


static class AuthenticationManager
{
    public static bool IsAuthenticated(User user)
    {
        bool authState = false;
        //check a cookie, or database, or session state, etc.
        return authState;
    }

A provider service to handle getting the token. This is the complex part if you wrote it yourself, since it needs to properly manage the network connection, protocol, certificates, trust, etc.

class Provider
{
    public string SignOnUri { get; set; }
    public string ProviderId { get; set; }
    public string ApplicationId { get; set;}
    public IEnumerable<Claim> GetClaims(string email)
    {
        //Set up network connection, such as to a remote Azure AD server
        var connection = GetConnected();
        //The provider knows which applications are allowed.
        if (!connection.IsValidApp(applicationId)) { throw new Exception("Invalid app");}
        //The user might need to sign in via a remote page NOT on our site.     
        var response = connection.Authenticate(email, SignOnUri);
        if (response.Authenticated)
        {
            //The framework-specific provider takes care of the messy work of parsing the claims.
            var claims = new List<Claim>();
            foreach (string clm in response.TokenClaimCollection)
            {
                claims.Add(new Claim() { Name = clm.Identifier, Value = clm.ValueUri }
            }
            var token = new Token() { TokenId = response.TokenGuid, Claims = claims};
            //store the token info somewhere, like in a cookie or database
            Session.Store(token);
            return token.Claims;
        }
        else
        {
            throw new Exception("Not a valid user.");
        }
    }
}

And here's how authentication might look, using that provider.

void Main()
{
    //The user who wants access.
    User user = new User() { Email = "jane.doe@example.com" };
    //We have a class that checks if the user's authenticated.
    if (!AuthenticationManager.IsAuthenticated(user))
    {
        //Set up a service class that does the work of communicating
        //with the STS. Behind the scenes, it could use WS-Federation, OpenId, SAML, etc.
        var sts = new Provider()
        {
            SignOnUri = "https://login.otherplace.com",
            ProviderId = "ef26632f-9af6-4ea0-8fcc-a8cee466cb6d",
            ApplicationId = "mysite.mydomain.com/isunique"
        };
        //Get the claims collection in our format. If we get this without error,
        //the user was authenticated. We don't care how! This is where the user
        //might be redirected to a login page.
        var claims = sts.GetToken(user.Email).Claims;
        //Check if the user can do something
        if (claims.First(c => c.Name == "Company").Value == "XyloTech")
        {
            //Show their reports
        }
    }
}

In the above method, we

  1. Found the user wasn't authenticated
  2. Instantiated our identity provider. This included a way to verify to the provider who we were, and vice versa. (Note: technically, the application trusts the STS, but the STS doesn't trust the application.)
  3. Asked the provider to authenticate the user. We may not know or care how this happens.
  4. Got back claims. Our provider class did the messy work of converting it to our class collection.
  5. Checked for a specific claim and took action.

In the next post, we'll look at some of the various namespaces Microsoft uses for claims-based identity.

--------------------

Tony Blake's Corvette, license plate: "SPIRIT"

image

No comments:

Post a Comment