Comment on page
OAuth
Tools:
- before OAuth, you would give a third-party application your password and allow it to act as you
- problems that would arise:
- applications would store users' passwords in plain text, making them a target for harvesting passwords
- applications can change the user's password through their account
- you can only revoke access by changing your password, something that users are typically reluctant to do
- services began to implement things similar to OAuth 1.0
- e.g. FlickrAuth by Flickr, AuthSub by Google, BBAuth by Yahoo, etc.
- wide variety of solutions that were incompatible and failed to address certain security considerations
- Blaine Cook, chief architect at Twitter wanted a better authentication method for the Twitter API
- We want something like Flickr Auth / Google AuthSub / Yahoo! BBAuth, but published as an open standard, with common server and client libraries, etc. - Blaine Cook, April 5, 2007
- the solution was OAuth 1
- OAuth 2.0 was created to be simpler and less confusing than OAuth 1
- many use cases, such as mobile applications, could not use OAuth 1
- due to disagreements on fundamental issues between the web and enterprise contributors, the standard was pulled into separate documents
- if someone wants to implement OAuth 2.0 for their web service, they need to synthesize information from a number of different sources
- implementers need to:
- read RFCs
- read drafts
- decide which token types and grant types to support
- decide token string size
- read security guidance and cautions in the document
- understand the implications of the decisions they have to make
- most web services that use OAuth come to the same decisions, resulting in very similar OAuth 2.0 APIs
- this book is a guide to building OAuth 2.0 APIs, with recommendations based on a majority of the live implementations
- every OAuth service requires you register a new application by signing up as a developer with the service
- registration process involves creating an account on the service's website and entering basic information about the application (name, website, logo, etc.)
- after registering the application, you receive a
client_id
and sometimes aclient_secret
to use when the app interacts with the service - you have to register one or more redirect URLs the application will use
- these are where the OAuth service will return the user to after they have authorized the application
- these have to be registered otherwise you can create malicious applications that steal user data
- OAuth APIs will only redirect users to a registered URL to prevent redirection attacks
- redirection attacks are where an authorization code/access token is intercepted by an attacker
- redirect URL must be an https endpoint
- if it is not https, an attacker may be able to intercept the authorization code and use it to hijack a session
- redirect URL validation must be an exact match
https://example.com/auth
would not matchhttps://example.com/auth?destination=account
- avoid query string parameters in your redirect URL
- the "state" parameter can be used to encode application state
- a string value that is passed into OAuth and is returned after the user authorizes the application
- e.g. you could encode a redirect URL in a JWT and parse it after the user is redirected to the appropriate location in your application
- how to access your data at an existing OAuth 2.0 server
- this chapter covers how to use the GitHub API to build an application that lists all repositories the logged-in user has created
- create an application on GitHub to get the client ID and client secret
- fill out the required information, including the callback URL
- after completing the form you'll see your client ID and secret
- client ID is public information used to build authorized URLs
- can be included in the JavaScript source code of a web page
- client secret must be kept confidential
Note: as the linked website has all of the code, I will simply paraphrase what needs to be done and paste the final code snippet at the end of Chapter 2.
- create a method that is a simple wrapper around cURL
- includes
Accept
andUser-Agent
headers that are required by GitHub's API - decodes the JSON response
- if we have an access token, it will send the proper OAuth header with the access token
- set up variables needed for OAuth, such as the
client_id
,client_secret
,authorized_url
,base_url
,token_url
, etc.- this varies by application
- set up
logged_in
andlogged_out
views to show whether the user is logged in or logged outlogged_out
view should contain a link to the login URL to start the OAuth process
- add
?action=login
in the query string to start the OAuth process - create a list of parameters that contain
state
to protect the client- state is a random string that the client generates and stores in the session
- when GitHub sends the user back with the state, we can verify that we initiated the request
- other parameters can include:
response_type
client_id
redirect_uri
scope
- send the user to the authorization URL
- user will see GitHub's OAuth authorization prompt
- after the user approves the request, they will be redirected back to our page with
code
andstate
parameters - next step: exchange authorization code for an access token
state
parameter is the same one we set in the initial authorization request- app should check that it matches to prevent attacks
- send a request to GitHub's token endpoint to exchange the authorization code for an access token
- request contains:
- public
client_id
- private
client_secret
- if the request is successful, GitHub will return an access token
- we store the access token in the session and redirect to the home page, where the user is logged in
- response from GitHub will look like
{
"access_token": "e2f8c8e136c73b1e909bb1021b3b4c29",
"token_type": "Bearer",
"scope": "public_repo,user"
}
- code will extract the access code and save it in the session
- next time the user visits the page, they will still be logged in
- the app can use the access token to make API requests
- in this case, the code will use the access token to list the user's repositories and link to each one
- that's it!
<?php
// Fill these out with the values you got from Github
$githubClientID = '';
$githubClientSecret = '';
// This is the URL we'll send the user to first to get their authorization
$authorizeURL = 'https://github.com/login/oauth/authorize';
// This is the endpoint our server will request an access token from
$tokenURL = 'https://github.com/login/oauth/access_token';
// This is the Github base URL we can use to make authenticated API requests
$apiURLBase = 'https://api.github.com/';
// The URL for this script, used as the redirect URL
$baseURL = 'https://' . $_SERVER['SERVER_NAME'] . $_SERVER['PHP_SELF'];
// Start a session so we have a place to store things between redirects
session_start();
// Start the login process by sending the user
// to Github's authorization page
if(isset($_GET['action']) && $_GET['action'] == 'login') {
unset($_SESSION['access_token']);
// Generate a random hash and store in the session
$_SESSION['state'] = bin2hex(random_bytes(16));
$params = array(
'response_type' => 'code',
'client_id' => $githubClientID,
'redirect_uri' => $baseURL,
'scope' => 'user public_repo',
'state' => $_SESSION['state']
);
// Redirect the user to Github's authorization page
header('Location: '.$authorizeURL.'?'.http_build_query($params));
die();
}
if(isset($_GET['action']) && $_GET['action'] == 'logout') {
unset($_SESSION['access_token']);
header('Location: '.$baseURL);
die();
}
// When Github redirects the user back here,
// there will be a "code" and "state" parameter in the query string
if(isset($_GET['code'])) {
// Verify the state matches our stored state
if(!isset($_GET['state'])
|| $_SESSION['state'] != $_GET['state']) {
header('Location: ' . $baseURL . '?error=invalid_state');
die();
}
// Exchange the auth code for an access token
$token = apiRequest($tokenURL, array(
'grant_type' => 'authorization_code',
'client_id' => $githubClientID,
'client_secret' => $githubClientSecret,
'redirect_uri' => $baseURL,
'code' => $_GET['code']
));
$_SESSION['access_token'] = $token['access_token'];
header('Location: ' . $baseURL);
die();
}
if(isset($_GET['action']) && $_GET['action'] == 'repos') {
// Find all repos created by the authenticated user
$repos = apiRequest($apiURLBase.'user/repos?'.http_build_query([
'sort' => 'created',
'direction' => 'desc'
]));
echo '<ul>';
foreach($repos as $repo) {
echo '<li><a href="' . $repo['html_url'] . '">'
. $repo['name'] . '</a></li>';
}
echo '</ul>';
}
// If there is an access token in the session
// the user is already logged in
if(!isset($_GET['action'])) {
if(!empty($_SESSION['access_token'])) {
echo '<h3>Logged In</h3>';
echo '<p><a href="?action=repos">View Repos</a></p>';
echo '<p><a href="?action=logout">Log Out</a></p>';
} else {
echo '<h3>Not logged in</h3>';
echo '<p><a href="?action=login">Log In</a></p>';
}
die();
}
// This helper function will make API requests to GitHub, setting
// the appropriate headers GitHub expects, and decoding the JSON response
function apiRequest($url, $post=FALSE, $headers=array()) {
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
if($post)
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post));
$headers = [
'Accept: application/vnd.github.v3+json, application/json',
'User-Agent: https://example-app.com/'
];
if(isset($_SESSION['access_token']))
$headers[] = 'Authorization: Bearer ' . $_SESSION['access_token'];
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
$response = curl_exec($ch);
return json_decode($response, true);
}
Last modified 2yr ago