A modern security model can be broken into three levels:- level one: you roll your own private secuirty
- level two: you integate with other identity providers (such as facebook)
- level three: you expose your own oAuth identity provider
Level 1
This is for anyone that implements their own closed scheme. The upside is that it's simple, downside is that it's connected to absolutely nothing. Don't get me wrong, this is extremely practical for private solutions that don't need to expose accounts or interoperate with a seperate service.
Level 2
If you've built an application and you want to make it so that users can login using their facebook credentials, you'll have to use a technology called "oAuth" as a "consumer". Its an industry standard way of integrating security into applications and it's pretty simple when you are the "consumer" and Facebook is the "provider" of the relationship. Lots of people do this. oAuth is implemented heavily in both mobile and web applications. To facilitate this, consumer API integrations are prevalent in pretty much every web framework out there - scala, php, java, node, .NET, and on down the line as well as every mobile application SDK.
Level 3
Ok, now this is stepping up to the big leagues. But before we get into the challenges, lets first define the needs. There are some very good reasons for needing to play the Level 3 role:- enterpises: as an enterprise you are creating a set of distinctly different applications and you want to provide a single security model across applications
- product companies: you want to expose your services so that other 3rd party applications can integrate with your Software as a Service (SaaS) platform
So this really applies to both the enterprise and the startup/product audiences. Doing this in a standard way, that reflects today's modern architecture, means that you'll have to really get your hands around "oAuth 2" from a provider standpoint. But, playing the role of "oAuth Provider", is a road far less traveled.
Options
There are plenty of BaaS (Back end as a Service) providers and API integration platforms that you can use instead of rolling your own. Although that number does appear to have gotten at least one vender smaller recently. But as I mentioned earlier, doing so isn't going to make you an "italian chef". So, why would you want to be an "italian chef" from a legitimate value prop standpoint that the business can understand?- you will not be dependant upon an outside provider
- you can add far more robust services in your security layer
- you can integrate easily with an existing ACL
Alternatively you could look back to some old SSO paradigns and leverage LDAP or portal based technology. That's a lonely place to go though. Old instrusive portals and mashup websites have been traded out for a higher activity of newer native mobile and responsive web applications. Those older paradigns open up the solution to trapping credentials, which is why the newer oAuth 2 approach is far more prevelant.
Sample Nodejs Provider
Being a big fan of nodejs development, I decided to take on the process of implementing my security scheme in nodejs with oauth2orize after evaluating a couple of different options. I really liked the way that it played with passportjs, the defacto standard for integrating authentication in nodejs applications. It also helped that there was a grant provider example and a consumer example for me to follow.
My needs are vast for the projected architecture and solving some pre-existing security issues is a top priority. To get an idea of the target roadmap, this is what my deployment is going to look like in a couple of years from now:As you can see this deployment will be made up of a combination of different frameworks and technology platforms including:It will even open up for 3rd party applications that will act as consumers of the security scheme.
Scenario
To understand what is going on from a sequence perspective, we are going to focus on just the intital deployment which includes:- browser
- CMS Application - a keystonejs web app (cms.barrelproofapps.com)
- Primary Application - a nodejs app that includes our oAuth Provider (primary.barrelproofapps.com)
Here is the sequence, we'll cover the details for each step in the request/response flow below
1.1 GET /auth
The consumer application constructs the appropriate url to start the grant and does so via a redirect.REQUEST:
GET /auth
HTTP/1.1
Host: cms.barrelproofapps.com
RESPONSE:
HTTP/1.1
302 Found
Location:
https://primary.barrelproofapps.com/auth/dialog/authorize?response_type=code&redirect_uri=https%3A%2F%2Fcms.barrelproofapps.com%2Fauth%2Fcallback&scope=email&client_id=abc123
1.2 GET /auth/dialog/authorize
This is the entry point for the grant. Since the user is NOT logged it, it will redirect the login. Note that internally the originating url is stored in the session so that upon login, it will redirect to this original path.REQUEST:
GET /auth/dialog/authorize?response_type=code&redirect_uri=https%3A%2F%2Fcms.barrelproofapps.com%2Fauth%2Fcallback&scope=email&client_id=abc123
HTTP/1.1
Host: primary.barrelproofapps.com
RESPONSE:
HTTP/1.1 302 Found
Location: /auth/login
1.3 GET /auth/login
Although ommitted, this response is a standard HTML login formREQUEST:
GET /auth/login
HTTP/1.1
Host: primary.barrelproofapps.com
RESPONSE:
HTTP/1.1
200 OK
Content-Type: text/html; charset=utf-8
...
2.1 POST /auth/login
Successful login will redirect to the original url provided and stored in the session from step 1.2.REQUEST:
POST /auth/login
HTTP/1.1
Host: primary.barrelproofapps.com
Content-Length: 38
username=test%40test.com&password=test
RESPONSE:
HTTP/1.1
302 Found
Location: /auth/dialog/authorize?response_type=code&redirect_uri=https%3A%2F%2Fcms.barrelproofapps.com%2Fauth%2Fcallback&scope=email&client_id=abc123
2.2 GET auth/dialog/authorize
Although ommitted, this is the HTML form to approve the grant accessREQUEST:
GET /auth/dialog/authorize?response_type=code&redirect_uri=https%3A%2F%2Fcms.barrelproofapps.com%2Fauth%2Fcallback&scope=email&client_id=abc123
HTTP/1.1
Host: primary.barrelproofapps.com
RESPONSE:
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
...
3.1 POST /auth/dialog/authorize
The user approves the grant and posts to the server which then redirects back to the callback url endpoint for the oAuth consumer.REQUEST:
POST /auth/dialog/authorize/decision
HTTP/1.1
Host: primary.barrelproofapps.com
Content-Type: application/x-www-form-urlencoded
transaction_id=RNw2bsLU
RESPONSE:
HTTP/1.1
302 Found
Location: https://cms.barrelproofapps.com/auth/callback?code=kpxeB0JGOY0CvPAu
3.2 GET /auth/callback
The callback url for the oAuth consumer, notice that the "code" is provided.REQUEST:
GET /auth/callback?code=kpxeB0JGOY0CvPAu
HTTP/1.1
Host: cms.barrelproofapps.com
RESPONSE:
HTTP/1.1 302 Found
Location: /
3.2.1 POST /auth/oauth/token
Now that the callback has provided the "code" for the oAuth grant, it's the responsibilty of the oAuth consumer to create a new access_token by signing the "code" with the oAuth client secret. The oAuth provider looks up the "code" and verifies that it's been properly signed with the appropriate client_secret and then issues an oAuth "access_token".REQUEST:
POST /auth/oauth/token HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Host: primary.barrelproofapps.com
grant_type=authorization_code&redirect_uri=https%3A%2F%2Fcms.barrelproofapps.com%2Fauth%2Fcallback&client_id=abc123&client_secret=ssh-secret&code=kpxeB0JGOY0CvPAu
RESPONSE:
HTTP/1.1 200 OK
Content-Type: application/json
{
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJfaWQiOiI1NmE3Y2M0ODI3MWFkNjZlNDg0ZjE0NmQiLCJpYXQiOjE0NTQzNjQxMTYsImV4cCI6MTQ1NDM4MjExNn0.rk30zTrmHsG065J04FlLxQAoJHoRlK9H53Br9k6bCOw",
"token_type": "Bearer"
}
3.2.2 GET /auth/api/userinfo
In this request the access_token is sent as a query string parameter, but it could also be sent as a Authorization Header with the Bearer token style. Note that all other REST API calls to the primary applicaiton will require this bearer token. Its bound to expire, so storing this in the session of the consumer web app is a good idea, as is calling the an oAuth "refreshtoken".REQUEST:
GET /auth/api/userinfo?access_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJfaWQiOiI1NmE3Y2M0ODI3MWFkNjZlNDg0ZjE0NmQiLCJpYXQiOjE0NTQzNjQxMTYsImV4cCI6MTQ1NDM4MjExNn0.rk30zTrmHsG065J04FlLxQAoJHoRlK9H53Br9k6bCOw
HTTP/1.1
Host: primary.barrelproofapps.com
RESPONSE:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{
"id": "56a7cc48271ad66e484f146d",
"email": "test@test.com",
"name": {
"last": "User",
"first": "Test"
},
"scope":"*",
"role":"user"
}
3.3 GET /
This renders the cms applications main page with the user logged in, HTML is ommitted for this response.REQUEST:
GET / HTTP/1.1
Host: cms.barrelproofapps.com
RESPONSE:
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
...
Summary
I had to tinker with the examples a bit to get them to work as I wanted. Although I'm not sharing that code, you'll want to do a bit of it for yourself as well. Some things are obvious like properly persisting the oauth2orize accesstokens, client, and accesscodes someplace instead of using the in-memory abstraction, token generation and expiration, verifying IP whitelists for the access token generation, etc. But all in all, if you are new to the process, just seeing the sequence diagram can be an extremely useful map to follow. This is good stuff, if you stand something like this up, you'll know oAuth inside & out afterwards.
If you are interested in learning more or want assitance with integrating oAuth in a solution, please contact us at http://barrelproofapps.com or send an email to info@barrelproofapps.com.