Skip to content
This repository was archived by the owner on Mar 13, 2020. It is now read-only.

Commit afa877c

Browse files
committed
Added refresh token ability
1 parent 31be223 commit afa877c

4 files changed

Lines changed: 188 additions & 14 deletions

File tree

‎README.md‎

Lines changed: 124 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -168,14 +168,14 @@ This doesn't do anything but display the authorization code returned by Azure, b
168168

169169
Now let's add a new function to the `oAuthService` class to retrieve a token. Add the following function to the class in the `./oauth.php` file.
170170

171-
#### New `getTokenFromAuthCode` function in `./oauth.php` ####
171+
#### New `getToken` function in `./oauth.php` ####
172172

173173
```PHP
174-
public static function getTokenFromAuthCode($authCode, $redirectUri) {
174+
public static function getToken($grantType, $code, $redirectUri) {
175175
// Build the form data to post to the OAuth2 token endpoint
176176
$token_request_data = array(
177-
"grant_type" => "authorization_code",
178-
"code" => $authCode,
177+
"grant_type" => $grantType,
178+
"code" => $code,
179179
"redirect_uri" => $redirectUri,
180180
"scope" => implode(" ", self::$scopes),
181181
"client_id" => self::$clientId,
@@ -228,7 +228,17 @@ public static function getTokenFromAuthCode($authCode, $redirectUri) {
228228

229229
This function uses cURL to issue the access token request to login.microsoftonline.com. The first part of this function is building the payload of the request, then the rest is using cURL to issue a POST request to the OAuth2 token endpoint. Finally, the results are parsed into an array of values using `json_decode`.
230230

231-
Let's see if this works. Replace the contents of the `./authorize.php` file with the following.
231+
Now add a wrapper function to pass the correct grant type in the `$grantType` parameter for exchanging an authorization code for a token.
232+
233+
#### New `getTokenFromAuthCode` function in `./oauth.php` ####
234+
235+
```PHP
236+
public static function getTokenFromAuthCode($authCode, $redirectUri) {
237+
return self::getToken("authorization_code", $authCode, $redirectUri);
238+
}
239+
```
240+
241+
Structuring the code this way will allow us to reuse the `getToken` function later. Let's see if this works. Replace the contents of the `./authorize.php` file with the following.
232242

233243
#### Updated contents of `./authorize.php` ####
234244

@@ -441,7 +451,113 @@ Finally, let's update the `./home.php` file to check for the presence of the acc
441451
</html>
442452
```
443453

444-
Now that we have an access token, we're ready to use the Mail API.
454+
### Refreshing the access token
455+
456+
Access tokens returned from Azure are valid for an hour. If you use the token after it has expired, the API calls will return 401 errors. You could ask the user to sign in again, but the better option is to refresh the token silently.
457+
458+
In order to do that, the app must request the `offline_access` scope. Add this scope to the `$scopes` array in `oauth.php`:
459+
460+
```PHP
461+
// The scopes the app requires
462+
private static $scopes = array("openid",
463+
"offline_access",
464+
"https://outlook.office.com/mail.read");
465+
```
466+
467+
This will cause the token response from Azure to include a refresh token. Let's update `authorize.php` to save the refresh token and the expiration time in a session cookie.
468+
469+
#### Updated contents of `./authorize.php` ####
470+
471+
```PHP
472+
<?php
473+
session_start();
474+
require_once('oauth.php');
475+
require_once('outlook.php');
476+
$auth_code = $_GET['code'];
477+
$redirectUri = 'http://localhost/php-tutorial/authorize.php';
478+
479+
$tokens = oAuthService::getTokenFromAuthCode($auth_code, $redirectUri);
480+
481+
if ($tokens['access_token']) {
482+
$_SESSION['access_token'] = $tokens['access_token'];
483+
$_SESSION['refresh_token'] = $tokens['refresh_token'];
484+
485+
// expires_in is in seconds
486+
// Get current timestamp (seconds since Unix Epoch) and
487+
// add expires_in to get expiration time
488+
// Subtract 5 minutes to allow for clock differences
489+
$expiration = time() + $tokens['expires_in'] - 300;
490+
$_SESSION['token_expires'] = $expiration;
491+
492+
// Get the user's email
493+
$user = OutlookService::getUser($tokens['access_token']);
494+
$_SESSION['user_email'] = $user['EmailAddress'];
495+
496+
// Redirect back to home page
497+
header("Location: http://localhost/php-tutorial/home.php");
498+
}
499+
else
500+
{
501+
echo "<p>ERROR: ".$tokens['error']."</p>";
502+
}
503+
?>
504+
```
505+
506+
Then let's add a function to call the `getToken` function to refresh our tokens.
507+
508+
#### New `getTokenFromRefreshToken` function in `./oauth.php` ####
509+
510+
```PHP
511+
public static function getTokenFromRefreshToken($refreshToken, $redirectUri) {
512+
return self::getToken("refresh_token", $refreshToken, $redirectUri);
513+
}
514+
```
515+
516+
Now let's add a helper function to automatically refresh the token if needed. Add the following function to `oauth.php`.
517+
518+
#### New `getAccessToken` function in `./oauth.php` ####
519+
520+
```PHP
521+
public static function getAccessToken($redirectUri) {
522+
// Is there an access token in the session?
523+
$current_token = $_SESSION['access_token'];
524+
if (!is_null($current_token)) {
525+
// Check expiration
526+
$expiration = $_SESSION['token_expires'];
527+
if ($expiration < time()) {
528+
error_log('Token expired! Refreshing...');
529+
// Token expired, refresh
530+
$refresh_token = $_SESSION['refresh_token'];
531+
$new_tokens = self::getTokenFromRefreshToken($refresh_token, $redirectUri);
532+
533+
// Update the stored tokens and expiration
534+
$_SESSION['access_token'] = $new_tokens['access_token'];
535+
$_SESSION['refresh_token'] = $new_tokens['refresh_token'];
536+
537+
// expires_in is in seconds
538+
// Get current timestamp (seconds since Unix Epoch) and
539+
// add expires_in to get expiration time
540+
// Subtract 5 minutes to allow for clock differences
541+
$expiration = time() + $new_tokens['expires_in'] - 300;
542+
$_SESSION['token_expires'] = $expiration;
543+
544+
// Return new token
545+
return $new_tokens['access_token'];
546+
}
547+
else {
548+
// Token is still valid, return it
549+
return $current_token;
550+
}
551+
}
552+
else {
553+
return null;
554+
}
555+
}
556+
```
557+
558+
This method checks the expiration time. If the current time is greater than the expiration, it calls our `getTokenFromRefreshToken` function to refresh. Otherwise, it just returns the cached token.
559+
560+
Now that we have an access token and we can refresh if needed, we're ready to use the Mail API.
445561

446562
## Using the Mail API ##
447563

@@ -501,7 +617,7 @@ Update `./home.php` to call the `getMessages` function and display the results.
501617
<?php
502618
}
503619
else {
504-
$messages = OutlookService::getMessages($_SESSION['access_token'], $_SESSION['user_email']);
620+
$messages = OutlookService::getMessages(oAuthService::getAccessToken($redirectUri), $_SESSION['user_email']);
505621
?>
506622
<!-- User is logged in, do something here -->
507623
<p>Messages: <?php echo print_r($messages) ?></p>
@@ -545,7 +661,7 @@ Update `./home.php` one final time to generate the table.
545661
<?php
546662
}
547663
else {
548-
$messages = OutlookService::getMessages($_SESSION['access_token'], $_SESSION['user_email']);
664+
$messages = OutlookService::getMessages(oAuthService::getAccessToken($redirectUri), $_SESSION['user_email']);
549665
?>
550666
<!-- User is logged in, do something here -->
551667
<h2>Your messages</h2>

‎authorize.php‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,14 @@
1010

1111
if ($tokens['access_token']) {
1212
$_SESSION['access_token'] = $tokens['access_token'];
13+
$_SESSION['refresh_token'] = $tokens['refresh_token'];
14+
15+
// expires_in is in seconds
16+
// Get current timestamp (seconds since Unix Epoch) and
17+
// add expires_in to get expiration time
18+
// Subtract 5 minutes to allow for clock differences
19+
$expiration = time() + $tokens['expires_in'] - 300;
20+
$_SESSION['token_expires'] = $expiration;
1321

1422
// Get the user's email
1523
$user = OutlookService::getUser($tokens['access_token']);

‎home.php‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
<?php
2222
}
2323
else {
24-
$messages = OutlookService::getMessages($_SESSION['access_token'], $_SESSION['user_email']);
24+
$messages = OutlookService::getMessages(oAuthService::getAccessToken($redirectUri), $_SESSION['user_email']);
2525
?>
2626
<!-- User is logged in, do something here -->
2727
<h2>Your messages</h2>

‎oauth.php‎

Lines changed: 55 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ class oAuthService {
77
private static $authorizeUrl = '/common/oauth2/v2.0/authorize?client_id=%1$s&redirect_uri=%2$s&response_type=code&scope=%3$s';
88
private static $tokenUrl = "/common/oauth2/v2.0/token";
99

10-
// The app only needs openid (for user's ID info), and Mail.Read
11-
private static $scopes = array("openid",
10+
// The scopes the app requires
11+
private static $scopes = array("openid",
12+
"offline_access",
1213
"https://outlook.office.com/mail.read",
1314
"https://outlook.office.com/calendars.read",
1415
"https://outlook.office.com/contacts.read");
@@ -23,12 +24,25 @@ public static function getLoginUrl($redirectUri) {
2324
error_log("Generated login URL: ".$loginUrl);
2425
return $loginUrl;
2526
}
26-
27+
2728
public static function getTokenFromAuthCode($authCode, $redirectUri) {
29+
return self::getToken("authorization_code", $authCode, $redirectUri);
30+
}
31+
32+
public static function getTokenFromRefreshToken($refreshToken, $redirectUri) {
33+
return self::getToken("refresh_token", $refreshToken, $redirectUri);
34+
}
35+
36+
public static function getToken($grantType, $code, $redirectUri) {
37+
$parameter_name = $grantType;
38+
if (strcmp($parameter_name, 'authorization_code') == 0) {
39+
$parameter_name = 'code';
40+
}
41+
2842
// Build the form data to post to the OAuth2 token endpoint
2943
$token_request_data = array(
30-
"grant_type" => "authorization_code",
31-
"code" => $authCode,
44+
"grant_type" => $grantType,
45+
$parameter_name => $code,
3246
"redirect_uri" => $redirectUri,
3347
"scope" => implode(" ", self::$scopes),
3448
"client_id" => self::$clientId,
@@ -77,6 +91,42 @@ public static function getTokenFromAuthCode($authCode, $redirectUri) {
7791

7892
return $json_vals;
7993
}
94+
95+
public static function getAccessToken($redirectUri) {
96+
// Is there an access token in the session?
97+
$current_token = $_SESSION['access_token'];
98+
if (!is_null($current_token)) {
99+
// Check expiration
100+
$expiration = $_SESSION['token_expires'];
101+
if ($expiration < time()) {
102+
error_log('Token expired! Refreshing...');
103+
// Token expired, refresh
104+
$refresh_token = $_SESSION['refresh_token'];
105+
$new_tokens = self::getTokenFromRefreshToken($refresh_token, $redirectUri);
106+
107+
// Update the stored tokens and expiration
108+
$_SESSION['access_token'] = $new_tokens['access_token'];
109+
$_SESSION['refresh_token'] = $new_tokens['refresh_token'];
110+
111+
// expires_in is in seconds
112+
// Get current timestamp (seconds since Unix Epoch) and
113+
// add expires_in to get expiration time
114+
// Subtract 5 minutes to allow for clock differences
115+
$expiration = time() + $new_tokens['expires_in'] - 300;
116+
$_SESSION['token_expires'] = $expiration;
117+
118+
// Return new token
119+
return $new_tokens['access_token'];
120+
}
121+
else {
122+
// Token is still valid, return it
123+
return $current_token;
124+
}
125+
}
126+
else {
127+
return null;
128+
}
129+
}
80130
}
81131
?>
82132

0 commit comments

Comments
 (0)