Created
April 15, 2019 19:40
-
-
Save mjameswh/e3ed9420b98cdb6b5c1e76940af789f6 to your computer and use it in GitHub Desktop.
Use an existing PHP application with login as an OAuth2 Authorization Server
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
// ... | |
// Verify that provided username and password are valid | |
if ( checkLogin($_POST['username'], $_POST['password']) ) { | |
+ // If a target URL has been set, then redirect the user to that URL. | |
+ if (isset($_SESSION['login_success_target'])) { | |
+ header('Location: ' . $_SESSION['login_success_target']); | |
+ unset($_SESSION['login_success_target']); | |
+ | |
+ } else { | |
// Redirect the user to the default application page | |
header('Location: application.php'); | |
+ } | |
} else { | |
// Deal with login error... | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
require("shared.inc.php"); | |
switch (true) { // Dummy switch to allow interrupting processing without using exit() | |
case true: // because PHP FPM doesn't like exit(). | |
// Extract bearer token | |
$auth_header = $_SERVER['HTTP_AUTHORIZATION']; | |
if (!preg_match('/Bearer\s(\S+)/', $auth_header, $matches)) { | |
error_log("oauth2-profile: no bearer token specified through HTTP authorization header"); | |
http_response_code(403); | |
break; | |
} | |
$bearer_token = $matches[1]; | |
// Decode and validate access token | |
$access_token_object = json_decode(decrypt($bearer_token)); | |
if (json_last_error() !== JSON_ERROR_NONE) { | |
error_log("oauth2-profile: bearer token could not be decrypted to a valid JSON document"); | |
http_response_code(403); | |
break; | |
} | |
// Make sure the token is valid for this context | |
if ( ($access_token_object->token_type !== 'access') | |
|| ($access_token_object->expiration < time())) { | |
error_log("oauth2-profile: access token is not valid: "); | |
error_log(" token_type = " . $authcode_token_object->token_type); | |
error_log(" client_id = " . $authcode_token_object->client_id); | |
error_log(" expiration = " . $authcode_token_object->expiration); | |
error_log(" current time = " . time()); | |
http_response_code(403); | |
break; | |
} | |
// Fetch user's profile from database | |
$user_id = $refresh_token_object->user_id; | |
$user_object = fetch_user_object($user_id); | |
if ($user_object === null) { | |
error_log("oauth2-profile: failed to retrieve user profile"); | |
http_response_code(403); | |
break; | |
} | |
// Output the profile response | |
header("Content-Type: application/json"); | |
header("Cache-Control: no-store"); | |
header("Pragma: no-cache"); | |
print('{' . "\n"); | |
print(' "email": "' . $user_object['email'] . '",' . "\n"); | |
print(' "lang": "' . $user_object['lang'] . '"' . "\n"); | |
print('}' . "\n"); | |
} | |
function fetch_user_object($user_id) { | |
// FIXME: fetch user object from database | |
return [ | |
"email" => "[email protected]", | |
"lang" => "fr" | |
]; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
require("shared.inc.php"); | |
switch (true) { // Dummy switch to allow interrupting processing without using exit() | |
case true: // because PHP FPM doesn't like exit(). | |
// client_id must match one of predetermined names, and client_secret | |
// must match the presestablished secret for the specified client_id | |
$client_id = $_POST["client_id"]; | |
$client_secret = $_POST["client_secret"]; | |
if (($client_id === "shelfpublication") && ($client_secret === "secretsecret")) { | |
// Good | |
} else { | |
error_log("oauth2-token: invalid client_id or client_secret"); | |
http_response_code(403); | |
break; | |
} | |
// check grant_type and associated parameters | |
$user_id = ""; | |
$assign_refresh_token = true; | |
$grant_type = $_POST["grant_type"]; | |
if ($grant_type === "authorization_code") { | |
// code is the code we generated earlier. It should match our formated syntax. | |
$authcode_token_object = json_decode(decrypt($_POST["code"])); | |
// If the token could not be decrypted to a valid JSON document, then it was probably junk | |
if (json_last_error() !== JSON_ERROR_NONE) { | |
error_log("oauth2-token: authorization code could not be decrypted to a valid JSON document"); | |
http_response_code(403); | |
break; | |
} | |
// Make sure the token is valid for this context | |
if ( ($authcode_token_object->token_type !== 'authcode') | |
|| ($authcode_token_object->client_id !== $client_id) | |
|| ($authcode_token_object->expiration < time())) { | |
error_log("oauth2-token: authorization code is not valid: "); | |
error_log(" token_type = " . $authcode_token_object->token_type); | |
error_log(" client_id = " . $authcode_token_object->client_id); | |
error_log(" expiration = " . $authcode_token_object->expiration); | |
error_log(" current time = " . time()); | |
http_response_code(403); | |
break; | |
} | |
$user_id = $authcode_token_object->user_id; | |
error_log("oauth2-token: allocating access token based on authorization code"); | |
} else if ($grant_type === "refresh_token") { | |
$refresh_token_object = json_decode(decrypt($_POST["refresh_token"])); | |
// If the token could not be decrypted to a valid JSON document, then it was probably junk | |
if (json_last_error() !== JSON_ERROR_NONE) { | |
error_log("oauth2-token: refresh token could not be decrypted to a valid JSON document"); | |
http_response_code(403); | |
break; | |
} | |
// Make sure the token is valid for this context | |
if ( ($refresh_token_object->token_type !== 'refresh') | |
|| ($refresh_token_object->client_id !== $client_id) | |
|| ($refresh_token_object->expiration < time())) { | |
error_log("oauth2-token: refresh token is not valid: "); | |
error_log(" token_type = " . $refresh_token_object->token_type); | |
error_log(" client_id = " . $refresh_token_object->client_id); | |
error_log(" expiration = " . $refresh_token_object->expiration); | |
error_log(" current time = " . time()); | |
http_response_code(403); | |
break; | |
} | |
$user_id = $refresh_token_object->user_id; | |
$assign_refresh_token = false; | |
if (!user_exists_and_is_enabled($user_id)) { | |
error_log("oauth2-token: could not reauthorize based on refresh token because user no longer exists"); | |
http_response_code(403); | |
break; | |
} | |
error_log("oauth2-token: allocating access token based on refresh token"); | |
} else { | |
error_log("oauth2-token: unsupported grant type: " . $grant_type); | |
http_response_code(400); | |
break; | |
} | |
// Create an access token | |
$access_token_object = [ | |
'token_type' => 'access', | |
'user_id' => $user_id, | |
'expiration' => time() + (60 * 30), // 30 mins | |
'client_id' => $client_id | |
]; | |
$access_token = encrypt(json_encode($access_token_object)); | |
// Create a refresh token, if appropriate | |
$refresh_token = ""; | |
if ($assign_refresh_token) { | |
$refresh_token_object = [ | |
'token_type' => 'refresh', | |
'user_id' => $user_id, | |
'expiration' => time() + (60 * 60 * 24 * 7), // 7 days | |
'client_id' => $client_id | |
]; | |
$refresh_token = encrypt(json_encode($refresh_token_object)); | |
} | |
// Output the access token response | |
header("Content-Type: application/json"); | |
header("Cache-Control: no-store"); | |
header("Pragma: no-cache"); | |
print('{' . "\n"); | |
print(' "access_token": "' . $access_token . '",' . "\n"); | |
print(' "token_type": "bearer",' . "\n"); | |
print(' "expires_in": 1800,' . "\n"); // 1800 sec == 30 min | |
if ($assign_refresh_token) { | |
print(' "refresh_token": "' . $refresh_token . '",' . "\n"); | |
} | |
print(' "scope": "profile"' . "\n"); | |
print('}' . "\n"); | |
} | |
function user_exists_and_is_enabled($user_id) { | |
// FIXME: assert that user is still authorized to login | |
return true; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment