OAuth 2.0 alone is an authorization protocol and can't be safely used on its own without adding additional protocol elements.
Some people might cal it a profile of OAuth, however we generally think of a profile in spec terms as a set of restrictions on a specification. As we need to add things to OAuth we are creating a new protocol using a profile of OAuth 2.0.
In fact if we look at the OAuth 2.0 specification itself we see.
Connect has dynamic Discovery and Registration, but Clients are not required to support that if they have a set of well known IdP that they are willing to restrict themselves to.
So to use Connect like this you would perform a out of band configuration for each of the IdP that the client intends to use. Preconfigured buttons for each IdP would be provided for each IDP.
It formulates a 302 redirect request to the known Authorization server endpoint of the issuer with it's client_id, and other OAuth 2.0 parameters.
Some people might cal it a profile of OAuth, however we generally think of a profile in spec terms as a set of restrictions on a specification. As we need to add things to OAuth we are creating a new protocol using a profile of OAuth 2.0.
In fact if we look at the OAuth 2.0 specification itself we see.
OAuth 2.0 provides a rich authorization framework with well-defined security properties. However, as a rich and highly extensible framework with many optional components, this specification is likely to produce a wide range of non-interoperable implementations. In addition, this specification leaves a few required components partially or fully undefined (e.g. client registration, authorization server capabilities, endpoint discovery).
This protocol was design with the clear expectation that future work will define prescriptive profiles and extensions necessary to achieve full web-scale interoperability.
It also has two incompatible token formats Bearer and MAC that you need to choose between before doing anything useful.
OAuth 2.0 is at the end of the day a toolkit for an authorization protocol designed to be used by many restful protocols.
So lets consider what the minimum amount we would need to specify to make a secure authentication protocol out of OAuth 2.0.
- You need is a protected resource that the Authorization server is protecting.
- The protected resource needs to make a user identifier available to the client.
- To be interoperable with more than one Identity provider you need some concept of a issuer, the user_id needs to be scoped to the issuer by the client.
- You need to define some authorization rules to say that only the resource owner can grant access to this endpoint.
- You need some way to prevent clients from replying access tokens at other clients to impersonate the user.
- If the protected resource, or resources have other information you need to define a OAuth scope to separate out the request to login vs other information or API the endpoints may be able to provide.
- Client must provide replay protection of code.
For OAuth 2.0 you need to profile some things for interoperability and security.
- Use bearer tokens (this presents opportunities for token theft that need mitigation)
- Require TLS for all IdP endpoints.
- Require client to track audience of access token, e.g. just because bad.com asks for a access token from google that you already have for the user, don't just give it to them.
- Require preregistration of all clients, so that they have a client_id and client_secret.
- Only use the code (formerly known as client-side) flow.
- Make Cross Site Scripting (XSRF) protection a MUST.
This is the bear minimum that you need to do. This is not sufficient for many important use cases like Ajax clients.
To make a real protocol rather than what is effectively a authentication
API for a single provider you need discovery of IdP and user
identifiers like email addresses and URI.
So if that were the requirement lets consider if OpenID Connect 1.0 can be used in this simplest of ways.
- Connect has a user-info endpoint returning a JSON object.
- The user_id element is always provided by the user-info endpoint.
- There is a Issuer that can be configured manually for every Oauth Authorization server and user-info endpoint. Connect uses a abstract URI identifier to allow IdP to reconfigure there endpoints without breaking clients.
- Connect only provides authorization tokens for the user-info endpoint if approved by the user, and not by a 3rd party like a friend as in some social graphs.
- If the Connect client can elect to only use the OAuth code flow.
- Connect defines a scope of openid for authentication, other scopes like profile provide additional claims about the user.
- Connect uses nonce to provide replay protection of id_tokens, because it supports multiple flows. A client just using code could use that to protect itself from replay.
Connect has dynamic Discovery and Registration, but Clients are not required to support that if they have a set of well known IdP that they are willing to restrict themselves to.
So to use Connect like this you would perform a out of band configuration for each of the IdP that the client intends to use. Preconfigured buttons for each IdP would be provided for each IDP.
Diagram by Amanda Anganes (mitre.org) |
Once the user clicks on a button the client stores some state in the
users browser and creates a handle for it(regular OAuth 2.0).
It formulates a 302 redirect request to the known Authorization server endpoint of the issuer with it's client_id, and other OAuth 2.0 parameters.
HTTP/1.1 302 Found
Location: https://server.example.com/op/authorize?
response_type=code
&scope=openid
&nonce=
&state=af0ifjsldkj
&client_id=s6BhdRkqt3
&redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb
This is just Oauth 2.0 with the addition of a defined scope and nonce
parameter. (nonce is used to provide replay protection for id_tokens,
we are not using that in this simple example so the value can be empty)
If the user authorizes the openid grant request the client gets back a standard OAuth 2.0 response:
The client needs to lookup the information for state that it stored in a cookie or someplace else to know who the issuer is and verify that the request to the Authorization server came from it and not via XSRF, logging the user in via a iframe in the background.
Once the client knows who the issuer is they send the code and there client secret to the issuer's Token Endpoint via a direct request from the server.
The response if the http basic authentication of the client_id and client_password are correct is:
Once I get my access token I can make a API request to the User Info Endpoint.
The response is going to be:
If I had also asked for the profile and email scopes and the user had granted access my response would look like:
No but mostly because Facebook is not supporting the current draft of OAuth 2.0 and OAuth Bearer Token.
However if they updated there version of OAuth it would work with only changes to the scope asked for and substituting the appropriate endpoint URI.
If we do the same example with the Facebook version of OAuth it would look like:
Authorization Request:
If the user authorizes the openid grant request the client gets back a standard OAuth 2.0 response:
HTTP/1.1 302 FoundIn OAuth 2.0 the reply doesn't contain any identifying information about who sent it.
Location: https://client.example.com/cb?
code=Qcb0Orv1zh30vL1MPRsbm-diHiMwcLyZvn1arpZv-Jxf_11jnpEX3Tgfvk
&state=af0ifjsldkj
The client needs to lookup the information for state that it stored in a cookie or someplace else to know who the issuer is and verify that the request to the Authorization server came from it and not via XSRF, logging the user in via a iframe in the background.
Once the client knows who the issuer is they send the code and there client secret to the issuer's Token Endpoint via a direct request from the server.
POST /token HTTP/1.1This is standard OAuth 2.0 nothing custom.
Host: server.example.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb
The response if the http basic authentication of the client_id and client_password are correct is:
HTTP/1.1 200 OKThis is a standard OAuth response with the addition of a id_token that we are going to ignore for our simple example. (The id_token is a signed token containing the user_id and other authentication information)
Content-Type: application/json
Cache-Control: no-store
Pragma: no-cache
{
"access_token": "SlAV32hkKG",
"token_type": "Bearer",
"refresh_token": "8xLOxBtZp8",
"expires_in": 3600,
"id_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOl
wvXC9zZXJ2ZXIuZXhhbXBsZS5jb20iLCJ1c2VyX2lkIjoiMjQ4Mjg5NzYxMDAxIiwiYXVkIj
oiaHR0cDpcL1wvY2xpZW50LmV4YW1wbGUuY29tIiwiZXhwIjoxMzExMjgxOTcwfQ.eDesUD0
vzDH3T1G3liaTNOrfaeWYjuRCEPNXVtaazNQ"
}
Diagram by Amanda Anganes (mitre.org) |
GET /userinfo?schema=openid HTTP/1.1The schema=openid parameter allows this to be added to existing endpoints that may use different schema by default. It is a fixed parameter that can be hard coded.
Host: server.example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJ ... fQ.8Gj_-sj ... _X
The response is going to be:
HTTP/1.1 200 OKYou now have the user_id and know the issuer so can look the user up and long them in.
Content-Type: application/json
{
"user_id": "248289761001",
}
If I had also asked for the profile and email scopes and the user had granted access my response would look like:
HTTP/1.1 200 OK Content-Type: application/jsonSo would this work with Facebook?
{ "user_id": "248289761001", "name": "Jane Doe" "given_name": "Jane", "family_name": "Doe", "email": "janedoe@example.com", "picture": "http://example.com/janedoe/me.jpg" }
No but mostly because Facebook is not supporting the current draft of OAuth 2.0 and OAuth Bearer Token.
However if they updated there version of OAuth it would work with only changes to the scope asked for and substituting the appropriate endpoint URI.
If we do the same example with the Facebook version of OAuth it would look like:
Authorization Request:
HTTP/1.1 302 FoundLocation: https://www.facebook.com/dialog/oauth?response_type=code
&client_id=s6BhdRkqt3
&redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb
- Difference from Connect - Facebook is not supporting state, so lack XSRF protection and force clients to have a different redirect_uri for each provider. It is not a problem for them as they assume only one identity provider. Sending no scope asks for a basket of default public claims including user_id. If you want email or address you need to include a request for those scope in the same way as Connect.
Authorization Response:
HTTP/1.1 302 Found
Location: https://client.example.com/cb?
code=Qcb0Orv1zh30vL1MPRsbm-diHiMwcLyZvn1arpZv-Jxf_11jnpEX3Tgfvk
- Difference from Connect - Facebook is not supporting state.
Access Token Request:
https://graph.facebook.com/oauth/access_token?
client_id=s6BhdRkqt3
&redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb
&client_secret=YOUR_APP_SECRET
&code=Qcb0Orv1zh30vL1MPRsbm-diHiMwcLyZvn1arpZv-Jxf_11jnpEX3Tgfvk
- Difference from Connect - Facebook is passing the client secret as a query parameter in a GET, This is not allowed in OAuth 2.0 it MUST be sent as HTTP Basic authentication or in the form body of a post to keep it from leaking. Other than the OAuth difference the request is exactly the same.
Access Token Response:
HTTP/1.1 200 OKCache-Control: no-storePragma: no-cacheaccess_token=SlAV32hkKG&expires_in=5108
- Difference from Connect - Honestly I don't know where they came up with URL encoding the response in the body. It has never been in OAuth 2.0 as far as I can tell. They are not returning the token type, it is just so wrong. It is the same info just in a wacky format missing some required parameters.
Graph API/Protected resource Request:
https://graph.facebook.com/me?access_token=ACCESS_TOKEN
- Difference from Connect - Facebook is passing the access token as a query parameter in a GET. Another security no-no highly discouraged by OAuth
Graph API/Protected Resource Response:
HTTP/1.1 200 OK
Content-Type: application/json
{
"id": "614345449",
"name": "John Bradley",
"first_name": "John",
"last_name": "Bradley",
"link": "https://www.facebook.com/ve7jtb",
"username": "ve7jtb",
"gender": "male",
"locale": "en_GB"
}
- Difference from Connect - Facebook is returning more info by default, though it is all public by there definition. Small difference in schema.
The Differences are not that large and mostly related to OAuth itself.
This is a very limited set of features. For a real protocol we need to
support Java Script clients in the browser, session management, fast
login without a second network call.
No comments:
Post a Comment