One of the issues I hear about hosting services in the cloud has to do with managing
Identity. Since the service isn’t local, it’s harder to tie it into services
like Active Directory. What do I mean by this?
I’m kind of particular how certain things work. I hate having more than one
set of credentials across applications. Theoretically since we can’t join our
Azure Servers to our domain, there’s a good chance we will need separate credentials
between our internal domain and Cloud Services. However, it’s possible to make
our Cloud Applications use our Active Directory credentials via a Claims Service.
With Federation Services it’s surprisingly easy to do. Yesterday we talked about installing
Active Directory Federation Services and federating an application. Today
we will talk about what it takes to get things talking between Azure and ADFS.
As a recap, yesterday we:
-
Installed prerequisites
-
Installed ADFS 2.0 on a domain joined server
-
Created a relying party
-
Created claims mappings to data in Active Directory
-
Created a simple Claims-Aware application
So what do we need to do next? There really isn’t much we need to do:
-
Build Azure App
-
Federate it using FedUtil.exe
Building an Azure application isn’t trivial, but we don’t need to know much to Federate
it.
How do we federate it? Follow these
steps providing the Azure details for the application URI and the Federation Metadata
from ADFS.
One of the gotcha’s with deploying to Azure though is that the Microsoft.IdentityModel
assembly is not part of the GAC, and it’s not in Azure builds. Therefore we
need to copy the assembly to the bin folder for deployment. We do that by going
to the Microsoft.IdentityModel reference properties and setting Copy Local to true:
That isn’t the only gotcha. We need to keep in mind how data is transferred
between Cloud and intranet. In most cases, nothing goes on behind the scenes;
it passes across the client’s browser through POST calls. If the client’s browser
is on the local intranet, when it hits the cloud app it will redirect to an intranet
location. This works because the client has access to both the cloud app and
can access ADFS. This isn’t necessarily the case with people who work offsite,
or are partners with the company.
We need to have the ADFS Server accessible to the public. This is kind
of an ugly situation. Leaving the politics out of this, we are sticking a domain
joined system out in the public that’s sole responsibility is authentication and identity
mapping.
One way to mitigate certain risks is to use an ADFS Proxy Service. This service
will sit on a non-domain joined system sitting on an edge network that creates a connection
to the ADFS Server sitting inside the corporate network. External applications
would use the Proxy service.
Installing the Proxy service is relatively simple, but a topic for another post.
From Microsoft Marketing, ADFS 2.0 is:
Active Directory Federation Services 2.0 helps IT enable users to collaborate across
organizational boundaries and easily access applications on-premises and in the cloud,
while maintaining application security. Through a claims-based
infrastructure, IT can enable a single sign-on experience for end-users to
applications without requiring a separate account or password, whether applications
are located in partner organizations or hosted in the cloud.
So, it’s a Token Service plus some. In a previous post I had said:
In other words it is a method for centralizing
user Identity information, very much like how the Windows Live and OpenID systems
work. The system is reasonably simple. I have a Membership data store
that contains user information. I want (n) number of websites to use that membership
store, EXCEPT I don’t want each application to have direct access to membership data
such as passwords. The way around it is through claims.
The membership store in this case being Active Directory.
I thought it would be a good idea to run through how to install ADFS and set up an
application to use it. Since we already discussed how to federate an application
using FedUtil.exe, I will let you go through the steps
in the previous post. I will provide information on where to find the Metadata
later on in this post.
But First: The Prerequisites
-
Join the Server to the Domain. (I’ve started the installation of ADFS three times
on non-domain joined systems. Doh!)
-
Install the latest .NET Framework. I’m kinda partial to using SmallestDotNet.com created
by Scott Hanselman. It’s easy.
-
Install IIS. If you are running Server 2008 R2 you can follow these
steps in another post, or just go through the wizards. FYI: The post installs
EVERY feature. Just remember that when you move to production. Surface
Area and what not…
-
Install PowerShell.
-
Install the Windows Identity Foundation: http://www.microsoft.com/downloads/details.aspx?FamilyID=eb9c345f-e830-40b8-a5fe-ae7a864c4d76&displaylang=en
-
Install SQL Server. This is NOT required. You only need
to install it if you want to use a SQL Database to get custom Claims data. You
could also use a SQL Server on another server…
-
Download ADFS 2.0 RTW: http://www.microsoft.com/downloads/details.aspx?familyid=118c3588-9070-426a-b655-6cec0a92c10b&displaylang=en
The Installation
Read the terms and accept them. If you notice, you only have to read half of
what you see because the rest is in French. Maybe the lawyers are listening…these
things are getting more readable.
Select Federation Server. A Server Proxy allows you to use ADFS on
a web server not joined to the domain.
We already installed all of these things. When you click next it will check
for latest hotfixes and ask if you want to open the configuration MMC snap-in.
Start it.
We want to start the configuration Wizard and then create a new Federation Service:
Next we want to create a Stand-alone federation server:
We need to select a certificate for ADFS to use. By default it uses the SSL
certificate of the default site in IIS. So lets add one. In the IIS Manager
select the server and then select Server Certificates:
We have a couple options when it comes to adding a certificate. For the sake
of this post I’ll just create a self-signed certificate, but if you have a domain
Certificate Authority you could go that route, or if this is a public facing service
create a request and get a certificate from a 3rd party CA.
Once we’ve created the certificate we assign it to the web site. Go to the website
and select Bindings…
Add a site binding for https:
Now that we’ve done that we can go back to the Configuration Wizard:
Click next and it will install the service. It will stop IIS so be aware of
that.
You may receive this error if you are installing on Server 2008:
The fix for this is here: http://www.syfuhs.net/2010/07/23/ADFS20WindowsServiceNotStartingOnServer2008.aspx
You will need to re-run the configuration wizard if you do this. It may complain
about the virtual applications already existing. You two options: 1) delete
the applications in IIS as well as the folder C:\inetpub\adfs; 2) Ignore the warning.
Back to the installation, it will create two new Virtual Applications in IIS:
Once the wizard finishes you can go back to the MMC snap-in and fiddle around.
The first thing we need to do is create an entry for a Relying Party. This will
allow us to create a web application to work with it.
When creating an RP we have a couple options to provide configuration data.
Since we are going to create a web application from scratch we will enter in manual
data. If you already have the application built and have Federation Metadata
available for it, by all means just use that.
We need a name:
Very original, eh?
Next we need to decide on what profile we will be using. Since we are building
an application from scratch we can take advantage of the 2.0 profile, but if we needed
backwards compatibility for a legacy application we should select the 1.0/1.1 profile.
Next we specify the certificate to encrypt our claims sent to the application.
We only need the public key of the certificate. When we run FedUtil.exe we can
specify which certificate we want to use to decrypt the incoming tokens. This
will be the private key of the same certificate. For the sake of this, we’ll
skip it.
The next step gets a little confusing. It asks which protocols we want to use
if we are federating with a separate STS. In this case since we aren’t doing
anything that crazy we can ignore them and continue:
We next need to specify the RP’s identifying URI.
Allow anyone and everyone, or deny everyone and add specific users later? Allow
everyone…
When we finish we want to edit the claim rules:
This dialog will allow us to add mappings between claims and the data within Active
Directory:
So lets add a rule. We want to Send LDAP Attributes as Claims
First we specify what data in Active Directory we want to provide:
Then we specify which claim type to use:
And ADFS is configured! Lets create our Relying Party. You can follow
these steps: Making
an ASP.NET Website Claims Aware with the Windows Identity Foundation. To
get the Federation Metadata for ADFS navigate to the URL that the default website
is mapped to + /FederationMetadata/2007-06/FederationMetadata.xml. In my case
it’s https://web1.nexus.internal.test/FederationMetadata/2007-06/FederationMetadata.xml.
Once you finish the utility it’s important that we tell ADFS that our new RP has Metadata
available. Double click on the RP to get to the properties. Select Monitoring:
Add the URL for the Metadata and select Monitor relying party. This will periodically
call up the URL and download the metadata in the event that it changes.
At this point we can test. Hit F5 and we will redirect to the ADFS page.
It will ask for domain credentials and redirect back to our page. Since I tested
it with a domain admin account I got this back:
It works!
For more information on ADFS 2.0 check out http://www.microsoft.com/windowsserver2008/en/us/ad-fs-2-overview.aspx or
the WIF Blog at http://blogs.msdn.com/b/card/
Happy coding!
The other day I kept hearing this noise from my neighbor. I couldn’t quite figure
it out, and naturally it was annoying. I didn’t do anything about it, but it
got me thinking about some random facts about sound and noise.
Medium
|
Velocity
|
(m/s)
|
(ft/s)
|
Aluminum
|
4877
|
16000
|
Brass
|
3475
|
11400
|
Brick
|
4176
|
13700
|
Concrete
|
3200 - 3600
|
10500 - 11800
|
Copper
|
3901
|
12800
|
Cork
|
366 - 518
|
1200 - 1700
|
Diamond
|
12000
|
39400
|
Glass
|
3962
|
13000
|
Glass, Pyrex
|
5640
|
18500
|
Gold
|
3240
|
10630
|
Hardwood
|
3962
|
13000
|
Iron
|
5130
|
16830
|
Lead
|
1158
|
3800
|
Lucite
|
2680
|
8790
|
Rubber
|
40 - 150
|
130 - 492
|
Steel
|
6100
|
20000
|
Water
|
1433
|
4700
|
Wood (hard)
|
3960
|
13000
|
Wood
|
3300 - 3600
|
10820 - 11810
|
-
The range of human hearing is 20 Hz – 20,000 Hz, however most people can only hear
between 40 Hz – 16,000 Hz
-
All frequencies are not equal. Our ears perceive certain frequencies to be louder
than others (found at Wikipedia http://en.wikipedia.org/wiki/Equal-loudness_contour):
-
Sound travels a smidge less than 1 foot per second at standard temperature and pressure.
Therefore if you need to place speakers in front of other speakers, you need to delay
them based on distance… 40 feet = ~40ms of delay.
-
If a speaker is placed in front of another without a delay, the sound from the speaker
farthest from you will sound similar to an echo. This is called the Haas effect.
However, most people don’t notice this until there is a 40ms gap between sounds, or
roughly 40 feet. After about 40ms of delay, the intelligibility of the sound
also decreases. I.e. it starts to degrade the quality, and you start having
trouble understanding what you hear.
Mostly useless facts, but they are fun to know.
At first glance the obvious answer is no, of course not. However, if we took
away the practical aspects of both, things might be a little different. Think
about it…
In medicine there is a standard of care: keep the person alive and healthy.
This is done through years of study, years of practice, not to mention the constant
need to keep up on latest drugs and treatments. There is however one constant.
The body, in all it’s natural wonders, only really has one specification. One
heart, two lungs, two kidneys, one liver, one stomach, one brain, etc.
Software development on the other hand, does not have one specification. Arguably,
there is no specification.
Software Development: Be able to develop anything and everything.
It’s an interesting concept. There are similarities between the jobs (as well
as between a lot of other jobs too) because both need to keep up on latest practices,
abide by rules, procedures, policies, and laws, all the while doing it well.
Developers, however, do not have the luxury of being able to follow a single set of
standards though. In our world, the righteous developers don’t just stick to
one platform or one language. You aren’t a true developer if you only know one
language, or can only code on one platform.
You aren’t a doctor if you can only save human lives. Wait. No.
That’s not right.
We are considered uninformed if we stick to one platform.
Practically speaking, being a doctor is without a doubt much harder than being a developer.
There are days though that I wish I were a doctor so I didn’t have to listen to other
developers complain about platforms or languages or methodologies or…
My point being: stop telling me that your way is better. I don’t care.
I really, really don’t. I will use what feels natural to me.
</rant>
A couple
posts back I had discussed how you would make an ASP.NET webforms application
claims aware. It was reasonably detailed an hopefully it was clear. I say that
because to make an MVC application Claims aware, you follow the exact same procedure.
The only difference is the small little chunk of code to see what claims were returned.
Just drop this little snipped into a view and you can muck about:
<ul>
<%
var claimsIdentity
= (System.Threading.Thread.CurrentPrincipal
as Microsoft.IdentityModel.Claims.IClaimsPrincipal)
.Identities[0];
foreach (var claim in claimsIdentity.Claims)
{%>
<li>
<%: claim.ClaimType %>
--
<%: claim.Value %>
<% } %>
</li>
</ul>
Just a quick little collection of useful code snippets when dealing with certificates.
Some of these don’t really need to be in their own methods but it helps for clarification.
Namespaces for Everything
using System.Security.Cryptography.X509Certificates;
using System.Security;
Save Certificate to Store
// Nothing fancy here. Just a helper method to parse strings.
private StoreName parseStoreName(string name)
{
return (StoreName)Enum.Parse(typeof(StoreName), name);
}
// Same here
private StoreLocation parseStoreLocation(string location)
{
return (StoreLocation)Enum.Parse(typeof(StoreLocation), location);
}
private void saveCertToStore(X509Certificate2 x509Certificate2, StoreName storeName, StoreLocation storeLocation)
{
X509Store store = new X509Store(storeName, storeLocation);
store.Open(OpenFlags.ReadWrite);
store.Add(x509Certificate2);
store.Close();
}
Create Certificate from byte[] array
private X509Certificate2 CreateCertificateFromByteArray(byte[] certFile)
{
return new X509Certificate2(certFile);
// will throw exception if certificate has private key
}
The comment says that it will throw an exception if the certificate has a private
key because the private key has a password associated with it. If you don't pass the
password as a parameter it will throw a System.Security.Cryptography.CryptographicException
exception.
Get Certificate from Store by Thumbprint
private bool FindCertInStore(
string thumbprint,
StoreName storeName,
StoreLocation storeLocation,
out X509Certificate2 theCert)
{
theCert = null;
X509Store store = new X509Store(storeName, storeLocation);
try
{
store.Open(OpenFlags.ReadWrite);
string thumbprintFixed = thumbprint.Replace(" ", "").ToUpperInvariant();
foreach (var cert in store.Certificates)
{
if (cert.Thumbprint.ToUpperInvariant().Equals(thumbprintFixed))
{
theCert = cert;
return true;
}
}
return false;
}
finally
{
store.Close();
}
}
Have fun!
Straight from Microsoft this is what the Windows Identity Foundation is:
Windows Identity Foundation helps .NET developers build claims-aware applications
that externalize user authentication from the application, improving developer productivity,
enhancing application security, and enabling interoperability. Developers can enjoy
greater productivity, using a single simplified identity model based on claims. They
can create more secure applications with a single user access model, reducing custom
implementations and enabling end users to securely access applications via on-premises
software as well as cloud services. Finally, they can enjoy greater flexibility in
application development through built-in interoperability that allows users, applications,
systems and other resources to communicate via claims.
In other words it is a method for centralizing user Identity information, very much
like how the Windows Live and OpenID systems work. The system is reasonably
simple. I have a Membership data store that contains user information.
I want (n) number of websites to use that membership store, EXCEPT I don’t want each
application to have direct access to membership data such as passwords. The
way around it is through claims.
In order for this to work you need a central web application called a Secure Token
Service (STS). This application will do authentication and provide a set of
available claims. It will say “hey! I am able to give you the person’s email
address, their username and the roles they belong to.” Each of those pieces
of information is a claim. This message exists in the application’s Federation
Metadata.
So far you are probably saying “yeah, so what?”
What I haven’t mentioned is that every application (called a Relying Party) that uses
this central application has one thing in common: each application doesn’t have to
handle authentication – at all. Each application passes off the authentication
request to the central application and the central application does the hard work.
When you type in your username and password, you are typing it into the central application,
not one of the many other applications. Once the central application authenticates
your credentials it POST’s the claims back to the other application. A diagram
might help:
Image borrowed from the Identity Training kit (http://www.microsoft.com/downloads/details.aspx?familyid=C3E315FA-94E2-4028-99CB-904369F177C0&displaylang=en)
The key takeaway is that only one single application does authentication. Everything
else just redirects to it. So lets actually see what it takes to authenticate
against an STS (central application). In future posts I will go into detail
about how to create an STS as well as how to use Active Directory Federation Services,
which is an STS that authenticates directly against (you guessed it) Active Directory.
First step is to install the Framework and SDK.
WIF RTW: http://www.microsoft.com/downloads/details.aspx?FamilyID=eb9c345f-e830-40b8-a5fe-ae7a864c4d76&displaylang=en
WIF SDK: http://www.microsoft.com/downloads/details.aspx?displaylang=en&FamilyID=c148b2df-c7af-46bb-9162-2c9422208504
The SDK will install sample projects and add two Visual Studio menu items under the
Tools menu. Both menu items do essentially the same thing, the difference being
that “Add STS Reference” pre-populates the wizard with the current web application’s
data.
Once the SDK is installed start up Visual Studio as Administrator. Create a
new web application. Next go to the Properties section and go into the Web section.
Change the Server Settings to use IIS. You need to use IIS. To
install IIS on Windows 7 check out this post.
So far we haven’t done anything crazy. We’ve just set a new application to use
IIS for development. Next we have some fun. Let’s add the STS Reference.
To add the STS Reference go to Tools > Add Sts Reference… and fill out the initial
screen:
Click next and it will prompt you about using an HTTPS connection. For the sake
of this we don’t need HTTPS so just continue. The next screen asks us about
where we get the STS Federation Metadata from. In this case I already have an
STS so I just paste in the URI:
Once it downloads the metadata it will ask if we want the Token that the STS sends
back to be encrypted. My recommendation is that we do, but for the sake of this
we won’t.
As an aside: In order for the STS to encrypt the token it will use a public key to
which our application (the Relying Party) will have the private key. When we
select a certificate it will stick that public key in the Relying Party’s own Federation
Metadata file. Anyway… When we click next we are given a list of available Claims
the STS can give us:
There is nothing to edit here; it’s just informative. Next we get a summary
of what we just did:
We can optionally schedule a Windows task to download changes.
We’ve now just added a crap-load of information to the *.config file. Actually,
we really didn’t. We just told ASP.NET to use the Microsoft.IdentityModel.Web.WSFederationAuthenticationModule
to handle authentication requests and Microsoft.IdentityModel.Web.SessionAuthenticationModule
to handle session management. Everything else is just boiler-plate configuration.
So lets test this thing:
-
Hit F5 – Compile compile compile compile compile… loads up http://localhost/WebApplication1
-
Page automatically redirects to https://login.myweg.com/login.aspx?ReturnUrl=%2fusers%2fissue.aspx%3fwa%3dwsignin1.0%26wtrealm%3dhttp%253a%252f%252flocalhost%252fWebApplication1%26wctx%3drm%253d0%2526id%253dpassive%2526ru%253d%25252fWebApplication1%25252f%26wct%3d2010-08-03T23%253a03%253a40Z&wa=wsignin1.0&wtrealm=http%3a%2f%2flocalhost%2fWebApplication1&wctx=rm%3d0%26id%3dpassive%26ru%3d%252fWebApplication1%252f&wct=2010-08-03T23%3a03%3a40Z (notice
the variables we’ve passed?)
-
Type in our username and password…
-
Redirect to http://localhost/WebApplication1
-
Yellow Screen of Death
Wait. What? If you are running IIS 7.5 and .NET 4.0, ASP.NET will probably
blow up. This is because the data that was POST’ed back to us from the STS had
funny characters in the values like angle brackets and stuff. ASP.NET does not
like this. Rightfully so, Cross Site Scripting attacks suck. To resolve
this you have two choices:
-
Add <httpRuntime requestValidationMode="2.0" /> to your web.config
-
Use a proper RequestValidator that can handle responses from Token Services
For the sake of testing add <httpRuntime requestValidationMode="2.0"
/> to the web.config and retry the test. You should be redirected to http://localhost/WebApplication1 and
no errors should occur.
Seems like a pointless exercise until you add a chunk of code to the default.aspx
page. Add a GridView and then add this code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Threading;
using System.IdentityModel;
using System.IdentityModel.Claims;
using Microsoft.IdentityModel.Claims;
namespace WebApplication1
{
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
IClaimsIdentity claimsIdentity = ((IClaimsPrincipal)(Thread.CurrentPrincipal)).Identities[0];
GridView1.DataSource = claimsIdentity.Claims;
GridView1.DataBind();
}
}
}
Rerun the test and you should get back some values. I hope some light bulbs
just turned on for some people :)
Earlier I had discussed how to bring the data from a database to the Windows Phone
7. Now I’d like to discuss how we think about displaying that data. It’s
important to know the relationships in the data so we can think about how everything
should be structured. We want to create a simple interface that is smooth and
logical. All data is different and this is by no means the “right” way to do
it, but it feels right to me.
This is industry standard stuff, and our data is modeled around it. Each layer
would be a collection of each sub-layer. So as an example There are multiple
tracks per breed, and multiple cards per track. A Card is a set of races for
a specific track. My best understanding is that we try very hard to stick to
one Card per day.
For this application though we aren’t going to assume anything.
It seems logical to me that we should mimic this structure in the logic, so lets try
and shoot for that. This is not the finished version, but of a mock-up of what
we might/should see.
Initial Page:
Once the breed has been selected we move onto selecting the track:
And so on…
In the future I will discuss the breakdown of each page and how the data is actually
displayed, plus how we switch between pages.
Interesting error found in explorer.exe. I tried hitting [Windows] + [E] and
got this message:
Kinda bizarre. I blame solar flares.
Earlier today I was talking with Cory Fowler about
an issue he was having with an Azure blob upload. Actually, he offered to help
with one of my problems first before he asked me for my thoughts – he’s a real community
guy. Alas I wasn’t able to help him with his problem, but it got me thinking
about how to handle basic Blob uploads.
On the CommunityFTW project I had worked on a few months back I used Azure as the
back end for media storage. The basis was simple: upload media stuffs to a container
of my choice. The end result was this class:
public sealed class BlobUploadManager
{
private static CloudBlobClient blobStorage;
private static bool s_createdContainer = false;
private static object s_blobLock = new Object();
private string theContainer = "";
public BlobUploadManager(string containerName)
{
if (string.IsNullOrEmpty(containerName))
throw new ArgumentNullException("containerName");
CreateOnceContainer(containerName);
}
public CloudBlobClient BlobClient { get; set; }
public string CreateUploadContainer()
{
BlobContainerPermissions perm = new BlobContainerPermissions();
var blobContainer = blobStorage.GetContainerReference(theContainer);
perm.PublicAccess = BlobContainerPublicAccessType.Container;
blobContainer.SetPermissions(perm);
var sas = blobContainer.GetSharedAccessSignature(new SharedAccessPolicy()
{
Permissions = SharedAccessPermissions.Write,
SharedAccessExpiryTime = DateTime.UtcNow + TimeSpan.FromMinutes(60)
});
return new UriBuilder(blobContainer.Uri) { Query = sas.TrimStart('?') }.Uri.AbsoluteUri;
}
private void CreateOnceContainer(string containerName)
{
this.theContainer = containerName;
if (s_createdContainer)
return;
lock (s_blobLock)
{
var storageAccount = new CloudStorageAccount(
new StorageCredentialsAccountAndKey(
SettingsController.GetSettingValue("BlobAccountName"),
SettingsController.GetSettingValue("BlobKey")),
false);
blobStorage = storageAccount.CreateCloudBlobClient();
CloudBlobContainer container = blobStorage.GetContainerReference(containerName);
container.CreateIfNotExist();
container.SetPermissions(
new BlobContainerPermissions()
{
PublicAccess = BlobContainerPublicAccessType.Container
});
s_createdContainer = true;
}
}
public string UploadBlob(Stream blobStream, string blobName)
{
if (blobStream == null)
throw new ArgumentNullException("blobStream");
if (string.IsNullOrEmpty(blobName))
throw new ArgumentNullException("blobName");
blobStorage.GetContainerReference(this.theContainer)
.GetBlobReference(blobName.ToLowerInvariant())
.UploadFromStream(blobStream);
return blobName.ToLowerInvariant();
}
}
With any luck with might help someone trying to jump into Azure.