.Nat Zone

Digital Identity et al.

OpenID Connect Stripped down to just “Authentication” (aka OAuth Authentication)

      2014/07/25

So, OpenID Connect provides a lot of advanced facilities to fulfill so many additional feature requested by the member community. It indeed is full of feature that is not Authentication. However, that does not mean that it cannot be used for the simple case of “Just Authentication”.

Indeed, it is actually quite simple to do it. I would use code flow as the base case in this article, because I believe that is the flow people should use for such cases.

Making an OpenID Connect request

In order for the client to make an OpenID Connect request, it needs to have the following basic OAuth 2.0 information about the server:

  • client identifier – A unique identifier issued to the client (RP) to identify itself to the authorization server. (e.g., 3214244 )
  • client secret – A shared secret established between the authorization server and client used for signing requests.
  • end-user authorization endpoint – The authorization server’s HTTP endpoint capable of authenticating the end-user and obtaining authorization. (e.g., https://server.example.com/authorize )
  • token endpoint – The authorization server’s HTTP endpoint capable of issuing access tokens.

In the simplest cases, this information is obtained by the client developer, having read the server’s documentation and pre-registered their application.

Then, for a bear bone authentication, you would put a link like this in the HTML page. [1]

<a href="https://server.example.com/authorize?grant_type=code&scope=openid&client_id=3214244&state=af1Ef">
 Login with Example.com
</a>

User initiates the login by clicking on the “Login with Example.com” link, and is taken to the server where he is asked username/password etc. if he is not logged into example.com yet. Once he agrees to login to  the RP, the browser is redirected back to the call back URL at the RP by 302 redirect. A PHP Server side code may look like:

<?php
 header("Location: https://client.example.com/cb?code=8rFowidZfjt&state=af1Ef");
?>

Note: ‘state’ is the parameter that is used to protect against CSRF in this case. It binds the request to the browser by storing in the cookie so that your site can compare the ‘state’ in the response to it to make sure that it is the response to your request. As such, it cannot be as static as above and has to be prepared afresh every time with sufficient entropy. 

That’s simple enough is it not?

Calling the Token endpoint to get id_token

Now that the RP has the ‘code’, let’s get the id_token, which is the user login information assertion, from the token endpoint. What do you do? Just POST it with HTTP Basic Auth using client_id, client_secret, and the code you got. So, using PHP and cURL, it would look like:

<?php
 $code = $_GET['code'];
 $ch = curl_init('https://server.example.com/token?code=' . $code);
 curl_setopt($ch, CURLOPT_HEADER, 1);
 curl_setopt($ch, CURLOPT_POST, TRUE); 
 curl_setopt($ch, CURLOPT_USERPWD, $client_id . ":" . $client_secret);
 curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
 $response = curl_exec($ch);
 ?>

The result, $response, would contain a JSON like this (line wraps for display purposes only):

{
 "access_token": "SlAV32hkKG",
 "token_type": "Bearer",
 "refresh_token": "8xLOxBtZp8",
 "expires_in": 3600,
 "id_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.
eyJpc3MiOiJodHRwczovL3NlcnZlci5leGFtcGxlLmNvbSIsInVzZXJfaWQiOiIyNDgy
ODk3NjEwMDEiLCJhdWQiOiJodHRwOi8vY2xpZW50LmV4YW1wbGUuY29tIiwiZXhwIjox
MzExMjgxOTcwfSA.
eDesUD0vzDH3T1G3liaTNOrfaeWYjuRCEPNXVtaazNQ"
}

Never mind what are "access_token", "token_type" etc. What you only care about is the "id_token".

"id_token" is encoded in a format called  JSON Web Token (JWT). JWT is the concatenation of “header”, “body”, “signature” by periods (.). Since you got this through TLS protected channel directly however, you do not need to check the signature for integrity, so you just take out the second portion of it and base64_decode it to get the information out of the id_token. So in PHP you may do like [2]:

<?php
$res = json_decode($response, true);
$id_token = $res['id_token'];
$id_array = mb_split(".", $id_token);
$id_body = base64url_decode($id_array[1]); 
?>

The resulting assertion, $id_body in the above example,  about the user (after pretty formatting)  is:

{
 "iss": "https://server.example.com",
 "user_id": "248289761001",
 "aud": "3214244",
 "iat": 1311195570,
 "exp": 1311281970
}

“iss” is showing the issuer of this token, in this case, the server.example.com. This is a name space of the  user_id, which is unique within the issuer and never reassigned.

When the client stores the user identifier, it MUST store the tuple of the  user_id and iss.

You had better check if “iss” value matches what you have learnt. (You should have learnt it from the developer site or via optional discovery.) If not, throw this away.

“aud” stands for “audience” and it shows who is the audience of this token. In this case, it is the RP itself. If it is different, you better throw it away.

“exp” is the expiry time of the token. If the current time is after “exp”, e.g., in PHP, if  $exp < time();  the RP should throw it away as well.

So, that is it. Now you know who is the user, i.e., you have authenticated the user.

All of the above in the form of code would be:

<?php
function check_id($id_body, $issuer, $client_id) {
    $idb = json_decode($id_body);

    if ($idb['iss'] != $issuer ) $err = true;
    if ($idb['aud'] != $client_id) $err = true;
    if ($idb['exp'] < time()) $err = true;
    if ($err) {
        return false;
    } else {
        return true;
    }
}
?>

Is it complex? Nah. It cannot get much simpler than this. It will not take more than ten minutes for the relying party to write a code just to authenticate the user.

 

 - identity, OAuth, OpenID Connect, security ,