-
Notifications
You must be signed in to change notification settings - Fork 1
Securing your API with OAuth 2.0
This guide will show you how to use the server library to secure a simple API with OAuth 2.0.
If you aren't using MySQL then please see this guide - https://github.com/php-loep/oauth2-server/wiki/Implementing-the-storage-interfaces.
This guide will use some functions from the Slim framework however the library is framework agnostic.
The recommended way of installing the library is via Composer.
If you already have a composer.json file in your root then add "league/oauth2-server": "2.*"
in the require object. Then run composer update
.
Otherwise create a new file in your project root called composer.json add set the contents to:
{
"require": {
"league/oauth2-server": "2.*"
}
}
Now, assuming you have installed Composer run composer install
.
To setup the database just import sql/mysql.sql.
To get up and running quickly we're going to use the built-in session PDO storage class. If you want to customise this or if you're not using MySQL then you need to implement the session interface - https://github.com/php-loep/oauth2-server/wiki/Implementing-the-storage-interfaces.
To use the PDO classes then add "zetacomponents/database": "1.4.6"
to your composer.json file and run composer update
.
Setting up the library is simple, just create a new instance of new League\OAuth2\Server\Resource
and pass in your storage models.
// Initiate the Request handler
$request = new League\OAuth2\Server\Util\Request();
// Initiate a new database connection
$db = new League\OAuth2\Server\Storage\PDO\Db('mysql://user:pass@localhost/oauth');
// Initiate the auth server with the models
$server = new League\OAuth2\Server\Resource(
new League\OAuth2\Server\Storage\PDO\Session($db)
);
Before your API responds you need to check that an access token has been presented with the request (either in the query string ?access_token=abcdef
or as an authorization header Authorization: bearer abcdef
).
If you’re using a framework such as Laravel or CodeIgniter you could use a route filter to do this, or have a custom controller which other controllers extend from. In this example I’m using the Slim framework and I’m going to create a simple route middleware which is run before each endpoint function.
$checkToken = function () use ($server) {
return function() use ($server)
{
// Test for token existance and validity
try {
$server->isValid();
}
// The access token is missing or invalid...
catch (League\OAuth2\Server\Exception\InvalidAccessTokenException $e)
{
$app = \Slim\Slim::getInstance();
$res = $app->response();
$res['Content-Type'] = 'application/json';
$res->status(403);
$res->body(json_encode(array(
'error' => $e->getMessage()
)));
}
};
};
When $server->isValid()
is called the library will run the following tasks:
- Check if an access token is present in the query string
- If not, check if a base64 encoded access token is contained in an authorization header.
- If not, throw League\OAuth2\Server\Exception\InvalidAccessTokenException`
- If not, check if a base64 encoded access token is contained in an authorization header.
- Check if the access token is valid with the database
- If not, throw
League\OAuth2\Server\Exception\InvalidAccessTokenException
- If not, throw
- If the access token is valid:
- Get the owner type (e.g. “user” or “client”) and their ID
- Get a list of any scopes that are associated with the access token
Assuming an exception isn’t thrown you can then use the following functions in your API code:
-
getOwnerType()
- This will return the type of the owner of the access token. For example if a user has authorized another client to use their resources the owner type would be “user”. -
getOwnerId()
- This will return the ID of the access token owner. You can use this to check if the owner has permission to do take some sort of action (such as retrieve a document or upload a file to a folder). -
getClientId()
- Returns the ID of the client that was involved in creating the session that the access token is linked to. -
getAccessToken()
- Returns the access token used in the request. -
hasScope()
- You can use this function to see if a specific scope (or several scopes) has been associated with the access token. You can use this to limit the contents of an API response or prevent access to an API endpoint without the correct scope.
This example endpoint will return a user’s information if a valid access token is present. If the access token has the user.contact
it will return additional information.
$app->get('/user/:id', $checkToken(), function ($id) use ($server, $app) {
$user_model = new UserModel();
$user = $user_model->getUser($id);
if ( ! $user)
{
$res = $app->response();
$res->status(404);
$res['Content-Type'] = 'application/json';
$res->body(json_encode(array(
'error' => 'User not found'
)));
}
else
{
// Basic response
$response = array(
'error' => null,
'result' => array(
'user_id' => $user['id'],
'firstname' => $user['firstname'],
'lastname' => $user['lastname']
)
);
// If the acess token has the "user.contact" access token include
// an email address and phone numner
if ($server->hasScope('user.contact'))
{
$response['result']['email'] = $user['email'];
$response['result']['phone'] = $user['phone'];
}
// Respond
$res = $app->response();
$res['Content-Type'] = 'application/json';
$res->body(json_encode($response));
}
});
In this example, only a user’s access token is valid:
$app->get('/user', $checkToken(), function () use ($server, $app) {
$user_model = new UserModel();
// Check the access token's owner is a user
if ($server->getOwnerType() === 'user')
{
// Get the access token owner's ID
$userId = $server->getOwnerId();
$user = $user_model->getUser($userId);
// If the user can't be found return 404
if ( ! $user)
{
$res = $app->response();
$res->status(404);
$res['Content-Type'] = 'application/json';
$res->body(json_encode(array(
'error' => 'Resource owner not found'
)));
}
// A user has been found
else
{
// Basic response
$response = array(
'error' => null,
'result' => array(
'user_id' => $user['id'],
'firstname' => $user['firstname'],
'lastname' => $user['lastname']
)
);
// If the acess token has the "user.contact" access token include
// an email address and phone numner
if ($server->hasScope('user.contact'))
{
$response['result']['email'] = $user['email'];
$response['result']['phone'] = $user['phone'];
}
// Respond
$res = $app->response();
$res['Content-Type'] = 'application/json';
$res->body(json_encode($response));
}
}
// The access token isn't owned by a user
else
{
$res = $app->response();
$res->status(403);
$res['Content-Type'] = 'application/json';
$res->body(json_encode(array(
'error' => 'Only access tokens representing users can use this endpoint'
)));
}
});
You might use an API function like this to allow a client to discover who a user is after they’ve signed into your authorization endpoint (see an example of how to do this here).
In this example, the endpoint will only respond to access tokens that are owner by client applications and that have the scope users.list
.
$app->get('/users', $checkToken(), function () use ($server, $app) {
$user_model = new UserModel();
$users = $user_model->getUsers();
// Check the access token owner is a client
if ($server->getOwnerType() === 'client' && $server->hasScope('users.list'))
{
$response = array(
'error' => null,
'results' => array()
);
$i = 0;
foreach ($users as $k => $v)
{
// Basic details
$response['results'][$i]['user_id'] = $v['id'];
$response['results'][$i]['firstname'] = $v['firstname'];
$response['results'][$i]['lastname'] = $v['lastname'];
// Include additional details with the right scope
if ($server->hasScope('user.contact'))
{
$response['results'][$i]['email'] = $v['email'];
$response['results'][$i]['phone'] = $v['phone'];
}
$i++;
}
$res = $app->response();
$res['Content-Type'] = 'application/json';
$res->body(json_encode($response));
}
// Access token owner isn't a client or doesn't have the correct scope
else
{
$res = $app->response();
$res->status(403);
$res['Content-Type'] = 'application/json';
$res->body(json_encode(array(
'error' => 'Only access tokens representing clients can use this endpoint'
)));
}
});
You might secure an endpoint in this way to only allow specific clients (such as your applications’ main website) access to private APIs.
Hopefully you can see how easy it is to secure an API with OAuth 2.0 and how you can use scopes to limit response contents or access to endpoints.