It was a crisp summer morning and I was on a stroll down the alley to meet an old friend of mine. After an exchange of pleasantries, Jane wanted to finish up a quick task before heading out for coffee. Jane runs a small business and is pretty busy when it comes to running it. So, I nodded and sat down to watch the show. She was using two different websites. She had to create identity in both the websites independently. Because each site had its own policies around passwords, she had to remember multiple passwords (which she kept forgetting, btw), and all the things that come with independent identity management.
The only thing I could tell her was “Man … you need SSO.”
Incidentally, Jane also uses QuickBooks as her accounting software. So when she told me that later in the afternoon she would have to create a Hubdoc account and somehow connect that with QuickBooks, again my reaction was “Why don’t you just use SSO?” From within QuickBooks, she could simply click on the Hubdoc app from the Apps tab, and that’s it — no further sign-up required. She would be logged into Hubdoc. And finally we had our time for coffee!
What is SSO?
SSO, or Single Sign On, is a mechanism to help create the feeling of a single ecosystem across multiple services for the end user by sharing key elements of an identity.
For customers, it is a delight. For the app, it is a conversion rather than a lead, without any friction. It is a win-win for all stakeholders.
Now that we’ve established that SSO is a way to do touchless sign-up and sign-in to an app’s site, let’s look at the cost savings.
Cost of user registration
As an app developer, you might ask “What is so complicated about implementing a user registration system?” After all, that is the most common interview question. So let’s take a look at the cost.
- Password management: This includes the cost of coming up with algorithms around what constitutes a “strong” password. If your app also happens to be global, then how the algorithms vary across various UTF-8 characters.
- Multi-factor authentication (MFA): Consider how, these days, even large organizations are being hacked easily. It is imperative that we no longer depend solely on a simple username and password. This also assumes that you have things like “Captcha” implemented, to make sure it is not a bot, but a human, that is registering.
- Account recovery: Assuming you have things like “Captcha” implemented to ensure the user is not a bot, you also need to implement security checks and measures to ensure only the rightful user can regain access to the account if they forget their password, etc.
- Lead-to-sale conversion: For every action that you require your customer to do before they can get started using your app, there is a potential of drop-off. Try to optimize the account setup and integration flow to keep the customer to the end of the process, and have the best chance of converting that lead to a sale.
Add all that up, and you will see what I mean by expensive.
OpenID as SSO
There are various SSO frameworks. In this post, I’m only talking about OpenID 2.0. I will touch upon the future of login OpenID Connect so you know it is a different protocol, even though there is similarity in the name.
How OpenID 2.0 works at Intuit
First, let’s get the terminology clear. The “relying party” (the app) relies on the OpenID provider (Intuit) to provide the single sign-on experience.
Relying Party (RP) – The app
OpenID Provider (OP) – Intuit
I will walk us through an example and we’ll talk about the various aspects of it. If you also want to read the spec, it is documented here.
Follow these steps:
- App wants the user to sign-up / sign-in; obviously, the app has to do something. This is the “request authentication” step.
- The app and Intuit will use the UserAgent (usually the browser) as a means of communication with the user in context. This is the “indirect communication” mechanism in the OpenID spec.
- But before the app can redirect to Intuit, the app should go to Intuit and get a temporary identifier for this request. This is to verify that a specific request is coming to Intuit from a known app. This is called “association handle“.
- Then the app can redirect the end user to Intuit with that handle and the requested parameters using “extension types“.
- Intuit will then have to get consent from the user.
- After gaining user consent, the requested parameters are returned along with the handle and a primary key called “openid_claimed_id”.
- The app can then redirect to this “openid_claimed_id” URL to get a valid XRDS document.
Here is how it looks on the wire
First, the setup:
http://localhost:8011 – Where the sample app is (the RP (relying party)). Source code for this is here on git.
https://vm-openid.intuit.com – Where the OpenID server is, exactly similar to openid.intuit.com (the OP, OpenId Provider (Intuit)).
Step 1: App gets an associate handle
Done by the “initialize” function: here in the sample app.
POST https://vm-openid.intuit.com/OpenId/Provider HTTP/1.1
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
HTTP/1.1 200 OK
Cache-Control: private, s-maxage=0
Date: Mon, 11 Apr 2016 23:33:06 GMT
Step 2: App redirects to OP with the associate handle
Done in the “initialize” function: here in the sample app.
This is a 302 to Intuit OpenID end point.
GET https://vm-openid.intuit.com/OpenId/Provider?openid.ns=http://specs.openid.net/auth/2.0&openid.claimed_id=http://specs.openid.net/auth/2.0/identifier_select&openid.identity=http://specs.openid.net/auth/2.0/identifier_select&openid.return_to=http://localhost:8011/verifyopenid.htm&openid.realm=http://localhost:8011/verifyopenid.htm&openid.assoc_handle=EP0i!IAAAAB68uD0k0zW2ySHYPVsvjCC-g8VcuEmmxcy472UZoaOxQQAAAAFvg6JpHlxBnq5x1udeXl4940HcHZVMmjJ5Af0LEMNSSaI_7anMoCTU9Ne5rX44ZN92izQmTjRHNCcBjRI3Uc9e&openid.mode=checkid_setup&openid.ns.ext1=http://openid.net/srv/ax/1.0&openid.ext1.mode=fetch_request&openid.ext1.type.FirstName=http://axschema.org/namePerson/first&openid.ext1.type.LastName=http://axschema.org/namePerson/last&openid.ext1.type.Email=http://axschema.org/contact/email&openid.ext1.type.RealmId=http://axschema.org/intuit/realmId&openid.ext1.required=FirstName,LastName,Email,RealmId&openid.ext1.count.Email=3 HTTP/1.1
User-Agent: Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.110 Safari/537.36
Accept-Encoding: gzip, deflate, sdch
This is a 302 back to the app end point Intuit OpenID end point mentioned in openid.return_to parameter
HTTP/1.1 302 Found
Cache-Control: private, s-maxage=0
Content-Type: text/html; charset=utf-8
Step 3: App validates the response claimed identity
This is where the XRDS document is returned from Intuit as a last-mile verification document.
Done by the “verifyOpenIDFromIntuit” function: here in the sample app
GET https://vm-openid.intuit.com/Identity-03d6580f-7fa0-4480-8aff-15cbeae3c270 HTTP/1.1
Accept: text/html; q=0.3, application/xhtml+xml; q=0.5, application/xrds+xml
HTTP/1.1 200 OK
Cache-Control: private, s-maxage=0
Content-Type: application/xrds+xml; charset=utf-8
<xrds:XRDS xmlns:xrds="xri://$xrds" xmlns:openid="http://openid.net/xmlns/1.0" xmlns="xri://$xrd*($v*2.0)"> <XRD> <Service priority="10"> <Type>http://specs.openid.net/auth/2.0/signon</Type> <Type>http://openid.net/extensions/sreg/1.1</Type> <Type>http://axschema.org/contact/email</Type> <URI>https://vm-openid.intuit.com/OpenId/Provider</URI> </Service> <Service priority="20"> <Type>http://openid.net/signon/1.0</Type> <Type>http://openid.net/extensions/sreg/1.1</Type> <Type>http://axschema.org/contact/email</Type> <URI>https://vm-openid.intuit.com/OpenId/Provider</URI> </Service> </XRD> </xrds:XRDS>
OpenID Connect vs OpenID 2.0
For completion sake, as the industry moved on to newer “authentication” standards, people realized that identity establishment and authentication can stay closer together than how OAUTH 1.0a and OpenID 2.0 were originally implemented. They didn’t need each other. Therefore, when OAUTH 2.0 came out, OpenID Connect was established as part of that flow as an add-on feature where user identity will be asserted and claims exchanged. The lingo there is a little different. The user information is usually returned as an encoded token meeting the JWT standards. The “sub” entity is the unique ID equivalent to the openid.claimed_id in OpenID 2.0.
I hope it is much clearer now what happens on the wire during an OpenID 2.0 handshake. If I scared you, then don’t worry — you don’t have to write these bits! There are OpenID libraries that live and breathe OpenID. For example, the sample code that I mentioned earlier is using the “openid4java” library. This post is intended to give you some insight into what is going on under the hood.