Profile picture of Travis Spencer Travis Spencer — Software Engineer

Home

Is OAuth Without Redirection Still OAuth?

Last updated:

There are various standard OAuth flows that use browser redirection. For instance, RFC 6749 and OpenID Connect together define the following flows that use redirection:

All of these redirect the browser back and forth. For instance, you can find this example of how the front-channel leg of the code flow may end (from the authorization server's perspective) in section 4.1.2 of RFC 6749:

HTTP/1.1 302 Found
Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz

With a tiny bit of HTTP know-how, this kind of redirection is easy to understand. With a bit more understanding, it can be seen that this isn't the only way, however.

Multiple times in my career, I have run into occasions where people thought that OAuth required redirection to be done in exactly this way. In fact, there are standards built atop RFC 6749 that require the use of the 302 HTTP status code to perform redirection because those specs misunderstand OAuth and think it's required. Thankfully, it's not.

This is important for a couple reasons:

  1. Security
  2. Interoperability

I won't say much about the security aspects of locking the redirect down to a 302, but I'll refer you to a paper about the dangers of doing so. Perhaps I'll write more about that in the future. For this post, however, I want to talk more about the interoperability issues of restricting redirection to this limited mechanism. To do that, we first need to see what the specs actually say.

Redirects in RFC 6749 and OpenID Connect

First, let's be clear that examples like the one above are examples and not requirements. This is clearly stated in section 1.7 of the OAuth RFC:

This specification makes extensive use of HTTP redirections, in which the client or the authorization server directs the resource owner's user-agent to another destination. While the examples in this specification show the use of the HTTP 302 status code, any other method available via the user-agent to accomplish this redirection is allowed and is considered to be an implementation detail.
See that? How redirection is done is an "implementation detail", as far as the OAuth framework is concerned. This means that all four examples in the RFC that use a 302 redirect status code are non-normative, meaning an authorization server can redirect the user agent with any status code it wishes.

The OpenID Connect core specification very clearly states that all redirect examples are non-normative; the 302s in that spec, therefore, are also implementation details. There is one normative mandate set by OpenID Connect though: If the client starts the original flow to the OpenID Connect Provider (OP) with a response_mode parameter that has a value of form_post, the OP must encode the response as an HTML form that is auto-submitted back to the client by the user agent. It doesn't say, however, what to do if the request that the OP is responding to was negotiated with the user agent to be some other representation than HTML. In lieu of any additional specification to guide the OP, it should probably fail to negotiate that non-HTML representation. This is up for debate, however, since the behavior is not stipulate.

Given all of this, the OAuth framework and OpenID Connect leave a lot of room for how to perform redirection. This is good because it allows many other specs to be built on top of them.

Redirection in the Hypermedia Authentication API

With the freedom provided in RFC 6749 and OpenID Connect, it's possible to support redirection in the hypermedia authentication API in a totally new way. This can be done in a standard-compliant manner, and results in increased interoperability with new clients that understand this API. Let me explain the implementation details, so you can see why I say this.

First, observe the final request made to the Curity Identity Server while performing the authorization code flow:

POST /authorize HTTP/2
Host: localhost:8443
Accept: application/vnd.auth+json
content-type: application/x-www-form-urlencoded
dpop: eyJq...

token=login_token&state=xyz            
        

At this point in the flow, login has completed. This was done using one of the many supported authentication methods that Curity has adapted to support the hypermedia login process. Upon success, a login token is submitted from our authentication service to our token service's authorization endpoint. If this token is valid, the DPoP proof token matches the attested client's private key, the state is OK, etc., then the authorization request will conclude.1 The details of these are not germane to this article's topic, but do take note that accept header value which is.

This accept header is saying that the user agent will accept only application/vnd.auth+json. If the server has a representation of the requested resource in this format, it must encode the response using this media type. If the "why" of this isn't clear to you, you'll need some more HTTP know-how in the area of content negotiation.

The response to this request is analogous to the example above from the OAuth spec. When a user agent requests application/vnd.auth+json like this, the response that's negotiated will look like this:

HTTP/2 200 OK
content-type: application/vnd.auth+json
content-length: 337

{
    "links": [
        {
            "href": myGoodApp://client-callback?code=InP32LtZPNFeQtKi60IBqMaBcpsujthD&state=foo",
            "rel": "authorization-response"
        }
    ],
    "metadata": {
        "viewName": "templates/oauth/success-authorization-response"
    },
    "type": "oauth-authorization-response",
    "properties": {
        "code": "InP32LtZPNFeQtKi60IBqMaBcpsujthD",
        "state": "foo"
    }
}

How very, very strange. That's not a redirect...or is it? Well, it's a 200 status code, so HTTP doesn't tell us it's a redirect. However, the content type does. The content of the body is encoded as application/vnd.auth+json which the user agent said it wanted. Happy to comply, the authorization server provided this state2 of the code flow represented according to the rules of this media type. This format has a type, and, in this case, that is oauth-authorization-response. This tells the user agent that it should send the properties included in the response, namely code and state, to the client's redirect endpoint. This "redirection endpoint URI MUST be an absolute URI...[and] MAY include an 'application/x-www-form-urlencoded' formatted...query component," says RFC 6749 section 3.2.1. Well look at that! The link's href value complies with this exactly. So, this response is standard compliant, even if it's different from what we're used to ☺

Here's the bigger question though: if the user agent doesn't make an HTTP request to this URL, is it non-compliant with the specification. The answer is no. To see why, you have to read very carefully what's in section 3.2.1 where it says:

After completing its interaction with the resource owner, the authorization server directs the resource owner's user-agent back to the client. The authorization server redirects the user-agent to the client's redirection endpoint previously established with the authorization server during the client registration process or when making the authorization request.
This means that if the user agent is the client (as a first-party app that uses the hypermedia API is), the user agent is already "back to the client" and no request to this URI is required.

"Now you're being a specification lawyer, and not interpreting the RFC according to its original intent," you may think. I'd argue back that the reason that the OAuth specification is so general is so it can be used as a framework for other specifications. My interpretation of redirection is perfectly in line with the spirit of OAuth. It is novel, however, and does require additional specifications around the hypermedia format, how to attest to the client's provenance if a request to its redirect URI doesn't involve a proof of its control over a part of the DNS namespace, etc. In any case, however, I don't think it's right to say that the hypermedia authentication API isn't OAuth because it doesn't use 302s.