In my last post I talked about trying out the Kerberos.NET sample project and mentioned that hitting the endpoint from a browser isn’t going to work because Active Directory doesn’t know about the application. Let’s see what we can do to fix this.

A Service Principal Name (SPN) is a unique identifier tied to an account in Active Directory. They exist in the form {service}/{identifier}, e.g. HTTP/foo.bar.com. They are used to uniquely identify a service that can receive Kerberos tickets. When a browser is prompted to Negotiate authentication it uses the requesting domain (minus scheme and port) to find an SPN with the service of “HTTP”. Therefore our domain of localhost would have an SPN of “HTTP/localhost”. Of course, localhost isn’t allowed, so we’ll need a real domain. We’ll get to that.

First, you need an Active Directory domain. Hyper-V is your friend. Go create a VM; I’ll wait. Okay, you’ve got your VM, now go add Active Directory (Add Role, promote, reboot).

Okay, now the fun part. Build the sample application and copy it over to your VM. Launch the application. You should see it waiting for requests. Try hitting the URL https://localhost:9000/ from within the VM. You’ll notice you still have the NTLM problem. This is because an SPN couldn’t be found for the service. We could add an SPN for localhost, but as I mentioned previously, localhost is a special case and can’t be used. In my case I just used a fake domain within my forest test.identityintervention.com. To make it accessible I just added a hosts file entry pointing to 127.0.0.1.

You may notice that starting a web listener on localhost won’t receive traffic from a custom domain — doh. You need to make a minor modification to the code and switch the “localhost” to “*” so the listener will listen on any domain.

string baseAddress = "https://localhost:9000/"; => "https://*:9000/"

The reason it’s not defaulted to * is because it requires administrative privileges to run, which is just annoying.

Rebuild the application and copy it over to the VM. Launch the application. You may have to run the application as Administrator if you get a permissions error.

At this point hitting the new endpoint will prompt for credentials and get us that same NTLM error, but that’s expected because we still haven’t created the SPN. Let’s go do that.

  1. Launch the Active Directory Users and Computers management console.
  2. Select View > Advanced Features (how many of you didn’t know that existed? It made my day the first time I learned about it).
  3. Navigate to the Users container and right click > New > User.
  4. Give it a name. I used “Service Host”.
  5. Hit next, and set the password to “P@ssw0rd!” without quotes.
  6. Save the user.
  7. Find our newly created user and right click > Properties > Attribute Editor (again, who didn’t know about that? Awesome, right?).
  8. Scroll down and find “servicePrincipalNames”. It’ll have an empty value. Click Edit.
  9. Add the SPN we defined earlier of “HTTP/test.identityintervention.com”
  10. Save all the changes

Adding a Service Principal Name

Alright, we now have an SPN for our service. You’ll notice the password you entered matches the password used in the sample code. This isn’t a coincidence — Active Directory uses this as the key to encrypt the ticket. Only the service should know the password, so only it can decrypt it. Now try hitting the our endpoint in the browser again. This time you’ll be prompted for credentials again, but the service will now be able to decrypt the ticket and you’ll end up with a result along the lines of:

Request: Mechanism: SPNEGO | MessageType: KRB_AP_REQ | SName: NT_SRV_INST, HTTP,test.identityintervention.com | 
Realm: IDENTITYINTERVENTION.COM | Ticket EType: RC4_HMAC_NT | Auth EType: RC4_HMAC_NT

Ticket: Flags: 1084293120 | CName: NT_PRINCIPAL, Administrator | CRealm: IDENTITYINTERVENTION.COM; | Version: 5 | 
Realm: IDENTITYINTERVENTION.COM | Sequence: 1966700342

That all looks about right, but being prompted for credentials is kind of annoying. Windows isn’t detecting that your test domain is within the intranet zone so it’s not going to automatically send a ticket (it won’t even check that the SPN exists). To fix this just add your test domain to the intranet zone on your computer.

  1. Open Internet Explorer
  2. Open the Settings > Security tab
  3. Select Local Intranet Zone and hit the Sites button
  4. Add the site https://test.identityintervention.com to the list and save it
  5. Close the browser and retry the request

Look Ma, no prompt!

Microsoft recently released the Azure AD Single Sign On preview feature, which is a way to support Kerberos authentication in to Azure AD. The neat thing about this is that you don’t need ADFS to have an SSO experience if you’ve already got AD infrastructure in place. It works the same way as in-domain authentication, via a Kerberos ticket granting scheme.

This is a somewhat confounding feature for anyone who has experience with Kerberos in Windows because every party needs to be domain-joined for Kerberos to work. This doesn’t seem possible in the cloud considering its a) not your box, and b) it’s over the public internet. Now, you could create one gigantic AD environment in Azure and create a million trusts to each local domain, but that doesn’t scale and managing the security of it would… suck. So how does Azure AD do it?

It’s deceptively simple, actually. Kerberos is a federated authentication protocol. An authority (Active Directory — AD) issues a ticket to a user targeting the service in use which is then handed off to the service by the user. The service validates that the ticket came from a trusted source (the authority — AD) and converts it into a usable identity for the user. The validation is actually pretty simple.

  1. Both the service and authority know a secret
  2. The authority encrypts the ticket against the secret
  3. The service decrypts the ticket using the secret
  4. The ticket is validated if decryption succeeds

It’s a little more complicated than that, but that’s the gist.

Nothing about this process inherently requires being domain-joined — the only reason (well… not the only reason) you join a machine to a domain is so this secret relationship can be kept in sync.

With this bit of knowledge in hand, it’s not that big a leap to think you can remove some of the infrastructure requirements and have AD issue a ticket to the cloud. In fact, if you’ve had experience with configuring SSO into apps running on non-Windows boxes you know it’s possible because those boxes aren’t domain joined.

So enough background… how does this work?

During installation and configuration:

  1. A Computer object is created in AD representing Azure AD called AZUREADSSOACC
  2. Two Service Principal Names (SPNs) are added to the account
    • HOST/aadg.windows.net.nsatc.net
    • HOST/autologon.microsoftazuread-sso.com
  3. A password is created for this account (the secret)
  4. The secret is sent to Azure AD
  5. Both domains of the SPNs are added as intranet zones to any machine doing SSO

Authentication works as follows:

  1. When a user hits login.microsoftonline.com and enters their username, an iframe opens up and hits the autologon domain
  2. The autologon domain returns a 401 with WWW-Authenticate header, which tells the browser it should try and authenticate
  3. The browser complies since the domain is marked as being in the intranet zone
  4. The browser requests a Kerberos ticket from AD using the current user’s identity
  5. AD looks up the requesting autologon domain and finds it’s an SPN for the AZUREADSSOACC computer
  6. AD creates a ticket for the user and targets it for the AZUREADSSOACC by encrypting it using the account’s secret
  7. The browser sends the ticket to the autologon domain
  8. The autologon domain validates the ticket by decrypting it using the secret that was synced
  9. If the ticket can be decrypted and it meets the usual validation requires (time, replay, realm, etc.) then a session key is pushed up to the parent window
  10. The parent window observes the session key and auto logs in

The only sort of synchronization that occurs is that initial object creation and secret generation. AD doesn’t actually care that the service isn’t domain joined.

Technically as far a AD knows the service is domain joined by virtue of it having a computer account and secret, but that’s just semantics. AD can’t actually manage the service.

That’s all there is to it.

There is a growing trend where authentication is occurring within browser controls in applications instead of through native UI. In general, this is good because it makes things like federation simpler, and lets you use different forms of authentication without requiring changes to native components.

This means any application you build can rely on a single authentication service and support a multitude of credential types without having to do anything in your own native app. Microsoft is all in with this through Azure AD, and through the ADAL (Azure Active Directory Authentication Libraries).

This is just rendered HTML.

This is just rendered HTML.

Of course, there are problems with that. Paramount is that on Windows you’re more or less stuck with using the Internet Explorer WebBrowser Control. Now, IE has come a long way over the last few years to be standards compliant and overall it works really well. The story, however, is a little different when you’re using the WebBrowser control. That’s because the control defaults to IE 7 compatibility mode, meaning any HTML rendered through the control is interpreted by a — in the literal sense — retarded rendering engine. It really doesn’t function well and has no support whatsoever for any newer technologies.

Alternate title: Debugging JavaScript in Embedded Browsers on Windows.

There are ways you can fix this by using the X-UA-Compatible header or meta tag, or through the FEATURE_BROWSER_EMULATION registry key, but sometimes they both just fall short and you’re stuck troubleshooting just what the heck is going on in the page — and you don’t get the fancy F12 tools since it’s hosted in a process separate from a browser.

So how do you debug this thing? Thankfully Microsoft has a lot of debuggers, and most of them are available through Visual Studio. The process is pretty simple.

First, you need to enable script debugging in Internet Explorer (or disable-disabling script debugging) by unchecking both “Disable script debugging” options.

Enable Script Debugging

Enable Script Debugging

A word of advice — check these again when you’re done otherwise you’re going to see a lot of annoying pop ups asking to debug things.

Next, open Visual Studio and open the debugger Attach to Process window. Open the code Type Selection and switch to Script debugging.

Script Debugging

Script Debugging

Now attach the debugger to the offending process.

You are offensive, sir.

You are offensive, sir.

And cause something to explode.

Let's go offend someone!

Let’s go offend someone!

And it explodes.

There goes the neighborhood.

There goes the neighborhood.

Now you can break into the code and figure out what’s going on. The nice thing is that it’s a full debugger, so you have access to all the usual tools like the console.

Consoling You as you debug.

Consoling You as you debug.

Have fun.