Awesome
PHPoAuthUserData
Extension library for Lusitanian/PHPoAuthLib to abstract the process of extracting user profile data from various OAuth providers (Facebook, Twitter, Linkedin, etc).
Contents
- Abstract
- License
- Installation
- Providers support
- Mapped fields and methods
- Examples
- How to contribute
- Contributors
- Tests
Abstract
OAuth 1 and 2 are great standard protocols to authenticate users in our apps and the library Lusitanian/PHPoAuthLib allow us to do it in a very simple and concise manner. Anyway we often need to extract various information about the user after he has been authenticated. Unfortunately this is something that is not standardized and obviously each OAuth provider manages user data in very specific manner according to its purposes.
So each provider offers specific APIs with specific data schemes to extract data about the authenticated user.
That's not a big deal if we build apps that adopts a single OAuth provider, but if we want to adopt more of them things can get really cumbersome.
Just to make things clearer suppose you want to allow users in your app to sign up with Facebook, Twitter and Linkedin. Probably, to increase conversion rate and speed up the sign up process, you may want to populate the user profile on your app by copying data from the OAuth provider user profile he used to sign up. Yes, you have to deal with 3 different sets of APIs and data schemes! And suppose you would be able to add GitHub and Google one day, that will count for 5 different APIs and data schemes... not so maintainable, isn't it?
Ok, relax... this library exists to ease this pain! It provides an abstraction layer on the top of Lusitanian/PHPoAuthLib library to extract user data from the OAuth providers you already integrated in your app.
It offers a uniform and (really) simple interface to extract the most interesting and common user data such as Name, Username, Id and so on.
Just to give you a quick idea of what is possible with the library have a look at the following snippet:
// $service is an istance of \OAuth\Common\Service\ServiceInterface (eg. the "Facebook" service) with a valid access token
$extractorFactory = new \OAuth\UserData\ExtractorFactory();
$extractor = $extractorFactory->get($service); // get the extractor for the given service
echo $extractor->getUniqueId(); // prints out the unique id of the user
echo $extractor->getUsername(); // prints out the username of the user
echo $extractor->getImageUrl(); // prints out the url of the user profile image
License
This library is licensed under the MIT license.
Installation
This library can be found on Packagist. The recommended way to install this is through composer.
Edit your composer.json
and add:
{
"require": {
"oryzone/oauth-user-data": "~1.0@dev"
}
}
And install dependencies:
$ curl -sS https://getcomposer.org/installer | php
$ php composer.phar install
Providers support
Currently there are extractors for the following providers:
- GitHub (Thanks to @cmfcmf)
- Harvest (Thanks to @mneuhaus)
More to come (obviously)! Want to contribute?
Mapped fields and methods
The library leverages around the concept of extractors. An extractor is a specific implementation of the logic to retrieve data for a given OAuth provider.
Each extractor can retrieve the following user data fields:
- uniqueId (string)
- username (string)
- firstName (string)
- lastName (string)
- fullName (string)
- email (string)
- location (string)
- description (string)
- imageUrl (string)
- profileUrl (string)
- websites (array)
- verifiedEmail (bool)
For each field you have convenience methods to get the value of the field or to check if it is supported by the given provider:
supportsUniqueId()
getUniqueId()
supportsUsername()
getUsername()
supportsFirstName()
getFirstName()
supportsLastName()
getLastName()
supportsFullName()
getFullName()
supportsEmail()
getEmail()
supportsLocation()
getLocation()
supportsDescription()
getDescription()
supportsImageUrl()
getImageUrl()
supportsProfileUrl()
getProfileUrl()
supportsWebsites()
getWebsites()
supportsVerifiedEmail()
isEmailVerified()
If you try to get a field that is not supported or it has not been set by the user on its profile you will get a null
value.
All the other other data offered by the service API is mapped under a special extra array accessible with the following methods:
supportsExtra()
getExtra($key)
getExtras()
You can have a look at the ExtractorInterface docblocks if you want a better understanding of what every method does.
NOTE: In many providers some user data fields are available only if you set the proper scopes/settings for your OAuth app.
Examples
Examples are available in the examples directory.
How to contribute
This project need a lot of work and Github is the perfect place to share the burden. So everyone is welcome to help and submit a pull request.
Keep in mind that the submitted code should be conform to PSR-2 standard and be tested with PHPUnit.
Probably you want to contribute by submitting a new Extractor. So let shed some light on some concepts involved in writing a new one.
Writing an Extractor
Extractors defines the logic to request information to a given service API and to normalize the received data according to a common interface. The most basic way to define an extractor is to write a class that implements the ExtractorInterface (that is pretty self-explanatory). You could extend the class Extractor that implements most of the needed code to get you started. Anyway, extractors should really extend the class LazyExtractor where possible because it acts as a boilerplate to define highly optimized extractors. This class easily allows you to implement extractors that lazy loads data (perform requests only when needed to) and caches data (does not make the same request more than once and avoids normalizing the same data more than once). Everything is done behind the scenes, so you'll need to focus only on methods that define how to make requests and how to normalize data.
To understand how to write a new extractor by adopting the LazyExtractor we need to clarify some concepts:
- Supported fields: an array of the fields (you should use field constants from the ExtractorInterface) that can be extracted.
- Loaders: methods whose responsibility is to trigger the proper request to the OAuth provider endpoint to load a specific set of raw data. Generally
you need to define a loader for each block of information that could be retrieved from the endpoint. this methods must have the suffix
Loader
in their name. Most of the service will allow you to retrieve all the user data with a single request, so, in this cases, you would have only a single loader method (eg:profileLoader
). - Normalizers: methods that accept raw data (the one previously fetched by some loader method) and uses it to extract the value for a given field.
Usually you have a normalizer for each supported field. Normalizers methods must have the suffix
Normalizer
(eg.uniqueIdNormalizer
ordescriptionNormalizer
). - LoadersMap: an array that associates supported fields (keys) to loaders methods (values). Loaders methods must be referenced without the
Loader
suffix. Most of the time, if you have only theprofileLoader
loader you will have an array with all fields mapping to the stringprofile
. - NormalizersMap: an array that associates supported fields (keys) to the related normalizer methods (values). Normalizers methods must be
referenced without the
Normalizer
suffix. It's highly suggested to use the same name of the field for its related normalizer, so, most of the time, you will end up by having an array that maps field constants to the same field constant (eg.array(self::FIELD_UNIQUE_ID => self::FIELD_UNIQUE_ID)
) for every supported field.
Once you defined Supported Fields, Loaders, Normalizers, Loaders Map and Normalizers Map from within your new extractor class you must
wire them to the underlying logic by passing them to the parent constructor. So if you defined methods such as getSupportedField
, getLoadersMap
and getNormalizersMap
you will end up with a constructor like this:
public function __construct()
{
parent::__construct(
self::getLoadersMap(),
self::getNormalizersMap(),
self::getSupportedFields()
);
}
A small example
I wrote a dedicated blog post to present a small example that explains, step by step, how to write a new extractor.
Contributors
Tests
To run the tests, you must install dependencies with composer install --dev