Getting started with OAuth 2.0 Explicit Flow

Updated Dani 4 Tallied Votes 1K Views Share

What is OAuth and why do I want to use it?

If you want to write an application that caters to existing members of a service, and you want your application to be able to access private areas of your end-users' profiles, or modify their accounts on their behalf, then you need OAuth. OAuth allows your application to automatically detect which member is using it without the member having to share anything about themselves. Additionally, it allows your application to authenticate itself to a service API with the member credentials of an end-user who is using it, without the need for the end-user to share their login credentials.

Essentially, you would send your application's end-user to a webpage hosted by the service where they are asked if they authorize your application to access their account. Once they say yes, then your application is given an access token which is unique to your application and the individual end-user. Whenever you need to access the API on behalf of the end-user, you send the access token along with the request.

Daniweb supports three different types of OAuth flows. There is an explicit flow (for serverside programming languages such as PHP or C#). There is an implicit flow (for clientside languages such as javascript with AJAX). And there is an out-of-band flow for desktop applications that don't have the ability to accept incoming HTTP requests. For the purpose of this tutorial, I am going to walk you through connecting to DaniWeb's API using OAuth using the serverside (explicit) flow and then, after, the desktop (OOB) flow.

Before we get started, here is a live, working php-based demo of our serverside OAuth implementation. Here is a javascript-based demo of our clientside implementation. You'll notice that has a slightly different flow than that used for non-browser based languages. When dealing with desktop applications, you would redirect your end-user to a webpage such as this one. As an end-user, once you authorize the demo to access your profile once, future accesses to your account from the demo application are entirely transparent.

How do I get started?

The first step to using OAuth is to register your application with the service in order to generate a client_id and client_secret unique to your application. The client ID and secret combination is necessary to authenticate yourself and your application when you ask your end-user for authorization to access their account. To register a new application to connect to DaniWeb's API, please visit this page.

The client_id and client_secret are hard-coded into the application to authenticate the credentials of the application itself. The credentials of the application's end-users are verified and the application's access is explicitely authorized during the OAuth process.

The OAuth 2.0 flow for the end-user is as follows:

  • Your application redirects its end-users to the service's authorization page.
  • If they are not currently logged into the service, they are prompted to either log in or register. Otherwise, this step is bypassed.
  • They are prompted to authorize yoru application to access their accounts before being redirected back to your application.
  • If they have previously authorized your application, the process is transparent and they are immediately redirected back.

From the OAuth process, the application is given an access token which can be used to set and retrieve data specific to the end-user.

Acquiring an access token with the explicit (serverside) flow

This example is written in php, but OAuth is language-agnostic and will work with any server side language.

Step 1

At the beginning of our php-based program, let's begin by hard-coding the client_id, client_secret and URL of the current webpage.

$client_id = 'YOUR CLIENT ID HERE';
$client_secret = 'YOUR CLIENT SECRET HERE';
$current_url = 'http://'.$_SERVER['HTTP_HOST'].$_SERVER['PHP_SELF'];

Firstly, you need to send your website visitor to the service's authorization page along with a way to get back. Upon authorizing that your application can access their account, if a valid URL or http://localhost:8080 is specified as the means of returning to your application, the end-user will be redirected back and a code will be passed along as a query string parameter. In our php script, we know that if we don't have that code query string parameter, we haven't yet done this first step, so let's do it ...

if (!isset($_REQUEST['code']))
{
    header("Location: https://www.daniweb.com/api/oauth?response_type=code&client_id=$client_id&redirect_uri=".urlencode($current_url));
}

What we essentially did was send the user to https://www.daniweb.com/api/oauth with the following GET (query string) parameters:

  • response_type (Required): Defines what we want to be returned to us, so we set this to code.
  • redirect_uri (Required): Set to your redirect URI, which is the way that the service returns the end-user to your web-based application.
  • client_id (Required): Set to your application's ID #.
  • state (Optional): Set to a unique anti-CSRF string. Any value which you pass in gets passed back as a query string parameter when the service redirects the end-user back to your Redirect URI.
  • scope (Optional): Set to a comma-delimited list of additional permissions that you want to request. For the DaniWeb API, the ability to view a member's private messages or chat on their behalf, for example, requires additional permission. By not granting these privileges by default (since they're unnecessary in most cases), end-users are more willing to grant access to a potentially untrusted application.
  • display (Optional, Default page): Defines whether the layout of the page that asks for authorization from the end-user should be designed to be viewed as a full page or in a popup window. It can be set to either page or popup depending on your needs.

Step 2

Upon authorizing that your application can access their account, the end-user will be redirected to your redirect_uri with the optional state as a query string parameter, and a code will also be passed along as a query string parameter. We need this code, so the next step is to retrieve it. In php, this is easy because we can access it by simply referencing $_REQUEST['code']. For the simplicity of this example, we'll skip doing so, but you will also want to ensure that the state string that you passed into the request is the same state string that is passed back to you to protect from CSRF attacks.

Step 3

Now that we have the code, we can exchange it for an access token. We need to send a POST request to exchange the code and your application's credentials (client_id and client_secret) for a temporary access token. The following POST parameters are required for our https://www.daniweb.com/api/access_token request:

  • code (Required): The code that is returned from the end-user authorization process.
  • redirect_uri (Required): The same redirect URI as we used above.
  • client_id (Required): Set to your application's ID #.
  • client_secret (Required): Set to your application's secret.
  • grant_type (Required): Defines the credentials we are using to request that our access token be granted. It must be set to authorization_code.

Within our php-based application, we can use cURL to issue a POST request. The DaniWeb API always returns JSON objects, so we can then use the json_decode() function to turn the API's response into a usable php object. The json_decode() function has an optional second parameter which, when set to true, decodes the JSON string into a multi-dimensional array instead of an object with properties. We're not using that here, though, but it's still worth noting.

// Initialize cURL to send a POST request
$ch = curl_init('https://www.daniweb.com/api/access_token');
curl_setopt($ch, CURLOPT_POST, true);

// Use the Code to acquire a temporary Access Token
//    to use for the current session
curl_setopt($ch, CURLOPT_POSTFIELDS, array(
    'code' => $_REQUEST['code'],
    'redirect_uri' => $current_url,
    'client_id' => $client_id,
    'client_secret' => $client_secret,
    'grant_type' => 'authorization_code'
));

// The HTTP request will result in a JSON object stored in a string
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$token_info = json_decode(curl_exec($ch));
$access_token = $token_info->access_token;

curl_close($ch);

Step 4

When we retrieved the access token, the token object that resulted from the decoded JSON string not only included our access token, but it also included a timestamp of when the access token expires along with a refresh token. In fact, the structure of the object looks like this:

{
    access_token => '1234567890abcdef'
    token_type => 'Bearer'
    expires => 1400000000
    expires_in => 86400
}

Again, this tutorial is specific to the DaniWeb API, but we do adhere to OAuth standards so there should not be much discrepancy when working with other services. The token_type will always be Bearer. The access_token is a very long string of characters. expires_in will always be set to 86400, which means that our access tokens always have a lifespan of 86,400 seconds, or 24 hours. expires will be a UNIX timestamp representation of 24-hours from now.

In most cases, you will have a web-based application that accesses the end-user's account within a timely manner after they have authorized you access to it. For example, if you are attempting to build a Log In with DaniWeb feature to replace or extend your website's own signup mechanism, then once they have authorized access, you can immediately retrieve their personal data such as their email address.

However, in some situations, once you have been granted authorization, you will want to perpetually access the end-user's account on their behalf behind the scenes, such as via a cron job or scheduled task. For example, you might want to do this if you create an application which emails its end-users relevant information based on their current DaniWeb activity. In such a case, your application will need to access the DaniWeb API using the end-users' access tokens at a time when the end-users are not themselves in front of their web browsers.

In such a case, because access tokens are short-lived, you will want to store the refresh token somewhere in your own database so that you can request new access tokens once they expire. After the first 24-hours are up, each time that you need to gain access to an active access token, you can request a new one. Just as we issued a request to https://www.daniweb.com/api/access_token above, to gain an access token, we'll do something similar to refresh it, albeit with slightly different POST parameters:

  • refresh_token (Required): The refresh_token that is returned from the end-user authorization process.
  • client_id (Required): Set to your application's ID #.
  • client_secret (Required): Set to your application's secret.
  • grant_type (Required): Defines the credentials we are using to request that our access token be granted. It must be set to refresh_token.

The JSON string returned can be decoded into an object that follows the same structure as defined above.

We can put everything together and we end up with the following workable demo:

<?php
// EXAMPLE OF OAUTH 2.0 EXPLICIT FLOW IMPLEMENTATION IN PHP
// Note that OAuth 2.0 can also be implemented clientside via javascript  

$client_id = '';
$client_secret = '';
$current_url = 'http://'.$_SERVER['HTTP_HOST'].$_SERVER['PHP_SELF'];

/***********************************************************************************/
/* THE END-USER NEEDS TO AUTHORIZE OUR APPLICATION SO WE CAN FETCH AN ACCESS TOKEN */
/***********************************************************************************/

// Send your website visitor to our authorization page along with a way
    // to get back
if (!isset($_REQUEST['code']))
{
    header("Location: https://www.daniweb.com/api/oauth?response_type=code&client_id=$client_id&redirect_uri=".urlencode($current_url));
}

// Upon authorizing your access, they will be redirected to your redirect_uri
//    and Code will be passed along as a query string parameter

// Initialize cURL to send a POST request
$ch = curl_init('https://www.daniweb.com/api/access_token');
curl_setopt($ch, CURLOPT_POST, true);

// Use the Code to acquire a temporary Access Token
//    to use for the current session
curl_setopt($ch, CURLOPT_POSTFIELDS, array(
    'code' => $_REQUEST['code'],
    'redirect_uri' => $current_url,
    'client_id' => $client_id,
    'client_secret' => $client_secret,
    'grant_type' => 'authorization_code'
));

// The HTTP request will result in a JSON object stored in a string
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$token_info = json_decode(curl_exec($ch));
$access_token = $token_info->access_token;

curl_close($ch);

/***********************************************************************************/
/* SUCCESS! WE HAVE AN ACCESS TOKEN! NOW WE CAN PASS IT INTO REQUESTS AND DO STUFF */
/***********************************************************************************/    

// Pass your Access Token into all API requests to authenticate yourself 
$output = json_decode(
    file_get_contents("https://www.daniweb.com/api/me?access_token=" . urlencode($access_token)), true);
echo 'Hi, '.$output['data']['username'].'!';

?>

Acquiring a code with desktop applications

The serverside implementation described above offers a php-based demo in which the application redirects the end-user to the service's authorization page, and, upon the end-user's acceptance, the authorization page then sends the user back to the application along with a code as a query string parameter. The next step is for the application to read the query string parameter and ultimately exchange it for an access token.

However, desktop applications don't easily have the ability to accept incoming HTTP requests. Therefore, while they would be able to redirect the end-user to the service's authorization page, without the complexity of running a local web server, they have no means of listening for and accepting the service's request for the application which includes the code.

The out-of-band implementation, or OOB, allows desktop applications that don't have the ability to accept incoming HTTP requests to still acquire an access token and make authenticated API calls.

Once you have a code, you can continue to use the standard explicit flow implementation to exchange it for an access token.

Step 1

From within your desktop or mobile application, launch a web browser and direct the end-user to https://www.daniweb.com/api/oauth with the following GET (query string) parameters:

  • response_type (Required): Defines what we want to be returned to us, so we set this to code.
  • redirect_uri (Required): Set to oob.
  • client_id (Required): Set to your application's ID #.

Step 2

The end-user will be asked to authorize your application. Upon acceptance, they will be redirected to a web page that will have the authorization code in the browser window titlebar. Additionally, in the document body, the end-user will be politely asked to copy/paste it into the application if prompted.

Step 3

If you launched the web page within an embedded web browser within your application, you can retrieve the code from the titlebar and immediately close the browser without prompting the end-user. Alternatively, your application can offer a form where an end-user can paste in the authorization code upon seeing the prompt to do so.

Step 4

Desktop applications will most likely want to take advantage of refresh tokens. Please follow up with the previously mentioned implementation instructions.

cereal commented: nice! +11
Dani 4,329 The Queen of DaniWeb Administrator Featured Poster Premium Member

We also have a tutorial about clientside (implicit) flow which I encourage javascript people to check out as a follow up to this tutorial :)

Member Avatar for diafol
diafol

I seem to get a

Notice: Trying to get property of non-object in C:\xampp\htdocs\daniweb\daniweb_api.php on line 35

and

Warning: file_get_contents(http://www.daniweb.com/api/me?access_token=): failed to open stream: HTTP request failed! HTTP/1.0 400 Bad Request in C:\xampp\htdocs\daniweb\daniweb_api.php on line 43
Hi, !

when I refresh a page with a "code" parameter

The code is identical to the above (with my client id and secret). I changed https to http - I'm assuming that this wouldn't make a difference.

Any ideas?

Line 34 and 35:

$token_info = json_decode(curl_exec($ch));
$access_token = $token_info->access_token;

I print_r'ed the $token_info and it was empty on refresh.

Line 43:

$output = json_decode(
file_get_contents("http://www.daniweb.com/api/me?access_token=" . urlencode($access_token)), true);

Any ideas?

Dani 4,329 The Queen of DaniWeb Administrator Featured Poster Premium Member

Will look at this within the hour.

Dani 4,329 The Queen of DaniWeb Administrator Featured Poster Premium Member

OK, so yeah, this is by design, unless I'm misunderstanding you. A single code can only be exchanged for an access token ONCE.

The first time you execute the page, you exchange the code for an access token, and then use that access token to retrive 'Me'.

The second time you execute the page with the same code, you try to exchange it for an access token, but you receive an error message back instead. You aren't accounting for this happening in your code, and as a result, your access token variable is now set to blank. You then try to retrieve 'Me' with no access token. Your php program sees a 400 Bad Request HTTP status because that's the error code we send you if you're trying to request something without giving us all the information we need.

Member Avatar for diafol
diafol

Thanks. I get that, so what's the fix? How to get the access token on subsequent page loads for use in queries? Use a session var?

Dani 4,329 The Queen of DaniWeb Administrator Featured Poster Premium Member

When you exchange a code for an access token, the resulting JSON gives you both an access token as well as a refresh token. Store the access token in the current session; it will work for up to an hour. Store the refresh token in your database; it can be perpetually exchanged for a new access token unless the end-user has deauthorized your app.

You currently have

$token_info = json_decode(curl_exec($ch));
$access_token = $token_info->access_token;

Obviously this snafu's because it's possible that curl_exec() is returning a JSON object that simply states an error message, and therefore $token_info->access_token doesn't exist, and it just goes downhill from there.

The logic should be something like this instead:

if (access token exists for current session)
{
    // We are all good, use it!
}
else
{
    if (database_contains_refresh_token)
    {
        // Generate new access token via our refresh token
        // Save access token to current session (i.e. a cookie with a 1-hr long expire time)
    }
    else
    {
        // Redirect end-user to www.daniweb.com/api/oauth to generate a new code
        // Exchange new code for access token and refresh token
        // Save access token to current session
        // Save refresh token in your database for the specific end-user (if you want to associate members in your database with specific DaniWeb members, so whenever they log onto your site, you know and have perpetual access to who they are on ours)
    }
}
Dani 4,329 The Queen of DaniWeb Administrator Featured Poster Premium Member

It should also be noted that the demo PHP outlined within this tutorial is the brute force method ... We simply bypass any methods of caching and always redirect the end-user to www.daniweb.com/api/oauth each time they load up the demo page, where they are issued a new code to be exchanged. We don't take into consideration the use of access tokens on subsequent pages the end-user may visit on our site, nor do we attempt to reuse an access token that might have been generated only moments earlier.

Member Avatar for diafol
diafol

thanks

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.