p7s1-ctf / 7pass-php-sdk
7Pass SDK for PHP
Requires
- php: >=5.5.0
- ext-curl: *
- lib-curl: *
- firebase/php-jwt: ~3.0
- guzzlehttp/guzzle: ~6.0
- phpseclib/phpseclib: 0.3.*
- tedivm/stash: 0.14.*
Requires (Dev)
- munkie/phpunit-teamcity-testlistener: dev-master#94bd42157940c305a8f02c3d8b0574e8529e1404
- php-vcr/php-vcr: 1.2.8
- php-vcr/phpunit-testlistener-vcr: ~1.1
- phpunit/phpunit: 4.8.*
README
7Pass PHP SDK is a PHP library for interacting with the 7Pass SSO service. You can use this library to implement authentication for your website and take advantage of the already existing features that 7Pass SSO offers.
Installation
Before you start working with the library, you need to make sure your system meets the required criteria. As of now, the library requires PHP 5.5.0 and newer with cURL support.
Note: Only TLS v1.2 connections are accepted on production and qa environments -
please make sure your system's cURL library supports it,
otherwise SSL connect error
exception is thrown.
The library is distributed as a composer package. If you haven't already, please install Composer using the official instructions. Once Composer is installed, you can install the library as follows:
$ composer require p7s1-ctf/7pass-php-sdk
This will automatically add the library to the list of your application's dependencies.
Running the example application
To demonstrate the functionality of the library, there's an interactive tutorial / guide available. Before we get started setting it up, you need your web application's client. The client represents the entity which is associated with a service you want to authenticate to.
To obtain the client credentials, you first need to contact the 7Pass Tech Team.
Once you have the credentials available, you can go ahead and install the dependencies:
$ composer install
Next, you can go to the public_html
directory and create the local
configuration file:
$ cd public_html
$ cp config.local.php.example config.local.php
Edit the config.local.php
file in your favorite editor and fill out
the details. You should have all of the parameters at your disposal
after your client is set up. For testing, keep the environment set to
qa
. Once that's done, you can start the application using the PHP's
built-in server:
$ php -S localhost:8000
The example application should be now available at
http://localhost:8000
. The application will guide you through the
most common use cases of the library and show you code examples and
server responses along the way.
API Usage
You strongly encouraged to go over the example application first. It will show you the API calls with more comments and real values. It will also show you the real responses from the 7Pass SSO service as you progress.
To use the library, it's necessary to initialize it with the credentials of the client we want to use. If you don't have the credentials yet, please see above.
- client_id (required)
- client_secret (required)
If you're starting the development, it's always a good idea to work against a non live instance of the 7Pass SSO service. To specify the instance (the environment) against which you want to issue the requests, you can pass an additional key called environment to the configuration. There're currently two environments running: QA and production. Don't forget to switch to the production version before you release your application to the public.
$config = [ 'client_id' => 'YOUR_CLIENT_ID', 'client_secret' => 'YOUR_CLIENT_SECRET', 'environment' => 'qa' // Optional, defaults to 'production' ]; // Creates the configuration object $ssoConfig = new P7\SSO\Configuration($config); // Pass the configuration to the SSO object $sso = new P7\SSO($ssoConfig);
For both environments there is a default host
URL where 7Pass API endpoints can be reached on.
For production
environment it is https://sso.7pass.de and for qa
a host is https://sso.qa.7pass.ctf.prosiebensat1.com.
If needed, this can be overwritten by specifying host
in the configuration as below:
$config = [
'host' => 'https://mysubdomain.7pass.de'
// ... other config values
];
Authentication flow
The authentication process is simple and the high level view is as follows: The user is redirected to the 7Pass SSO service using a specially crafted URL and he/she signs in (or signs up). Once the user has finished the process, he/she is redirected back to your application with a special code in the URL. The application will then use the code in order to get the user's details. The process may vary depending on the passed options.
1. Get a redirect URL
The library automatically handles the generation of the URL to which
the user needs to be redirected. The only required parameter is the
redirect_uri
URL. The URL needs to be absolute and can be arbitrary
(given that it is registered to the client) but will by convention
lead to the same host and a route called "callback".
Use of state parameter
is optional but recommended to avoid CSRF attacks -
the value is usually stored in the session and is also used with $sso->authorization()->callback()
method when handling the callback request.
$callbackUri = 'https://example.com/callback'; $options = [ 'redirect_uri' => $callbackUri, // Required. 'scope' => 'openid profile email', // Optional, default value. 'response_type' => 'code' // Optional, default value. 'state' => $sessionState // Optional, but recommended to avoid CSRF attacks ]; $redirectUrl = $sso->authorization()->authorizeUri($options);
The library will automatically set the client_id
and nonce
(a
unique request identifier) parameters automatically.
2. Redirect an user
Now it's time to redirect the user to the generated URL. In plain PHP,
you can set the Location
header:
header('Location: ' . $redirectUrl); exit;
3. Handling 7Pass callback
After the user has finished with the sign in/up dialog, he/she has been redirected to the redirect_uri URL with the outcome of the sign in process. The user might have successfully authenticated but also might have decided to cancel the process or some other error might have happened. Therefore it's important have proper error handling.
Whenever an error occurs, there will be two query parameters present
in the URL - error and error_description
. The error parameter
contains an error code and the error_description contains a human
readable description of the error. Handle the error and display
appropriate message to your end-user.
You might choose to handle an error manually as below. Otherwise in case of an error,
calling $sso->authorization()->callback()
method would throw an
AuthorizeCallbackException.
if(!empty($_GET['error'])) { $error = $_GET['error']; $errorDescription = $_GET['error_description']; // Handle the error and display appropriate message to your end-user. }
The library handles retrieving the tokens on its own, you
just need to provide the redirect_uri
and also query parameters
from the request which should include code
. This allows you to retrieve the
tokens which can be used to fetch the actual information
about the user later. These tokens are specific to the particular user and
are private. You need to keep them secured and do not share them with
anybody.
$sessionState
parameter is optional but same should be provided
if used with $sso->authorization()->authorizeUri()
method.
The AuthorizeCallbackException
is thrown when state value provided and the one retrieved from query parameters $_GET does not match.
$tokens = $sso->authorization()->callback($callbackUri, $_GET, $sessionState);
The received response will have the following structure. Run the example application to see the real values.
P7\SSO\TokenSet( [access_token] => ACCESS_TOKEN [token_type] => 'Bearer' [refresh_token] => REFRESH_TOKEN [expires_in] => 7200 [id_token] => JWT_STRING [id_token_decoded] => DECODED_JWT, [received_at] => RECEIVED_AT_TIMESTAMP )
Note: The id_token_decoded
value is decoded from the id_token
field and verified. If token verification fails, the
P7\SSO\Exception\TokenVerificationException
exception is thrown.
Further, the call might throw P7\SSO\Exception\ApiException
in
case the code
has already been used or is otherwise invalid.
4. Caching an access token
The tokens are represented in a single object of type
P7\SSO\TokenSet
. You can serialize the object directly, however, we
recommend that you first convert the TokenSet
into a simpler array
object and serialize it instead. As soon as you need the TokenSet
again, you can pass the array object into its constructor.
// Serialize the TokenSet object and store it in e.g. the current session $_SESSION['tokens'] = $tokens->getArrayCopy(); // Deserialize and get the TokenSet object again $tokens = new \P7\SSO\TokenSet($_SESSION['tokens']);
Access tokens are valid for certain amount of time specified in
seconds in the expires_in
field. Once the access token expires, it
can no longer be used. You can obtain a new one using the refresh
token as follows:
if($tokens->isAccessTokenExpired()) { $tokens = $sso->authorization()->refresh([ 'refresh_token' => $tokens->refresh_token ]); }
Note: refresh()
method above also accepts P7\SSO\TokenSet
object as an argument.
5. Calling our API endpoints
Now that we're sure the tokens are up to date, we can start making requests to the 7Pass SSO service to get the user data.
Same as with the previous example, run the example application to see the real server response.
$accountClient = $sso->accountClient($tokens); $response = $accountClient->get('me');
The 7Pass SSO service offers quite a few of these endpoints. To learn more about them, you can go to the official documentation's overview.
Client Credentials requests
The library supports multiple types of "clients". These clients generally differ in the required configuration parameters and afterwards in the functionality they provide. The "client credentials" client is a special kind of client which is not associated with a user account and can be only used to call the "clients" APIs.
You can see all of the available endpoints in the
documentation
with the accessType parameter equal to client
.
$config = [ 'environment' => 'qa', // Optional, defaults to 'production', 'client_id' => 'CLIENT_ID', 'client_secret' => 'CLIENT_SECRET' ]; // Creates the configuration object $ssoConfig = new P7\SSO\Configuration($config); // Pass the configuration to the SSO object $sso = new P7\SSO($ssoConfig); // Get the tokens using the client credentials grant type $tokens = $sso->authorization()->clientCredentials(); // Use the client $client = $sso->clientCredentialsClient($tokens); $response = $client->post('checkPassword', [ 'password' => 'PASSWORD' ]);
Device flow
With device flow people can easily and safely log into apps and services with their 7Pass account on devices with limited input or display capabilities. This includes Smart TVs, digital photo frames, or Internet of Things devices.
The device (your application) instructs the end-user to use another computer or device and connect to 7Pass to approve the access request. Since your application cannot receive incoming requests, it polls the 7Pass authorization server repeatedly until the end-user completes the approval process.
Your application initiates the flow by requesting a set of verification codes from the authorization server by making an HTTP POST request to the token endpoint.
$deviceCodeResponse = $sso->authorization()->deviceCode();
$deviceCodeResponse
would be stdClass object like the following:
stdClass(
[code] => CODE // used along with poll requests (see below)
[user_code] => Q9CFLH // the code which user should enter on LINK page
[expires_in] => 600 // expiration of the code (in seconds)
[interval] => 5 // recommended interval for repeating poll requests (in seconds)
[link] => LINK_URL // verification URL user should use to authenticate
[link_qr] => IMAGE_URL // image URL of QR code encoded link
)
The user should now be prompted to visit link
URL on another device to
enter user_code
. link_qr
is URL of an image representing QR code encoded link
URL.
This could be used by users using mobile devices, they can scan it and have link
URL opened
in their mobile browsers without manually typing it themselves.
While user tries to authenticate on link
URL, your application polls 7Pass server repeatedly.
Recommended interval is specified in interval
value.
$code
in the example below can be either $deviceCodeResponse
object received from $sso->authorization()->deviceCode()
method call,
array with code
item or simply a string with code
value.
// repeat every [interval] seconds.
try {
$response = $sso->authorization()->deviceCodePoll($code);
if($response instanceof \P7\SSO\TokenSet) {
// stop polling - user has authorized from another device successfully
}
// $reponse === false in case 'authorization_pending' response is received
} catch(ApiException $e) {
// handle the error appropriately
// $e->getError() is set to one of error codes 'code_expired', 'slow_down', 'invalid_grant'
}
Backoffice requests
The library can also be used to perform "backoffice" requests. These
requests are initiated without direct involvement of users and are
meant for administrative purposes. Your client needs to have the
backoffice_code
grant type allowed. You also need to know the ID of
the user you want to work with.
Backoffice requests are used to make an API calls on behalf of other users. To get access token for these requests you need to use special grant type 'backoffice_code' providing an account_id. Upon successful authentication you get a same token set as using standard flow described above.
$config = [ 'environment' => 'qa' // Optional, defaults to 'production', 'service_id' => 'SERVICE_ID', // Required for backoffice access 'backoffice_key' => 'BACKOFFICEC_KEY' // Required for backoffice access ]; // Creates the configuration object $ssoConfig = new P7\SSO\Configuration($config); // Pass the configuration to the SSO object $sso = new P7\SSO($ssoConfig); // Get the tokens using the backoffice $tokens = $sso->authorization()->backoffice([ 'account_id' => 'account_id' // Required, the ID of the user you want to access ]); // Use the client as you normally would when using the standard access $accountClient = $sso->accountClient($tokens); $response = $accountClient->get('me');
The response will be as usual. Once you get the tokens, the 7Pass SSO service will act as if the access token has been obtained using the "standard" way.
Client/backoffice registration
When you register new users using the client or backoffice registration API, you might want to bounce them to the 7Pass SSO service so that the user's session is created and the user logged in.
This SDK provides a method called autologinUri()
which can be used to generate the redirect (bounce) URL.
The method accepts a TokenSet
as its first parameter. You can retrieve the user's tokens when using the registration API
by providing the scope
parameter. See the registration API
documentation for more details. response_type
defaults to none
but can be set to any other
supported type if needed so.
// $client is an instance of ApiClient created using $sso->clientCredentialsClient() or $sso->backofficeClient() methods.
$response = $client->post('registration', [
'scope' => 'openid profile email'
// other parameters
]);
$tokens = TokenSet::receiveTokens($response);
$callbackUri = 'https://example.com/callback';
$uri = $sso->authorization()->autologinUri($tokens, [
'redirect_uri' => $callbackUri, // Required
'state' => $sessionState // Optional but recommended to use
], [
'remember_me' => true // Default value: false
])
// Redirect user to $uri
As with other ordinary authorize requests, you can use Authorization::callback()
method to handle a callback request.
The method returns null
if response_type
was set to none
.
$tokens = $sso->authorization()->callback($callbackUri, $_GET, $sessionState);
Caching
Before the library can work properly, it needs to fetch the OpenID configuration from the configured instance of the 7Pass SSO service. To make sure the information is not downloaded every time, the library uses a configurable caching mechanism.
The default expiration time is set to 1 hour and can be customized by
passing cache_config_openid_ttl
value to the $config
settings (in
seconds).
The library internally computes a hash out of all of the parameters passed into the Configuration's constructor and uses it as the cache key for the configuration. This means the library will automatically rediscover the configuration in case the configuration changes.
Be aware however that in case the configuration is changed back to
some previously used state and the computed configuration's key
corresponds to an existing cache item, you might get a stale
configuration. If you expect the remote OpenID configuration to
change, you can either purge the cache and let the library rediscover
it itself or you can conditionally run the rediscover()
method.
Under the hood, it uses the Stash Caching Library library and tries to use the Apc driver first. If not avaible, it will use the Filesystem driver instead.
If desired, you can configure your own cache driver as follows:
$config = [ // ... other configuration settings 'cache_config_openid_ttl' => 3600 // sets cache expiration time to 1 hour ]; $ssoConfig = new P7\SSO\Configuration($config); $driver = new Stash\Driver\Memcache(['servers' => ['127.0.0.1', '11211']]); $ssoConfig->setCachePool(new Stash\Pool($driver)); $sso = new P7\SSO($ssoConfig);
To manually refresh the cache use the rediscover
method. This is not needed under normal circumstances.
$sso->getConfig()->rediscover();
If you have any questions or something's not working as expected, please do not hesitate to contact the 7Pass Tech Team.
Running the tests
The library uses PHPUnit for testing. The recommended version is 4.8 although the tests may run successfully on an older version of the 4 series as well.
$ composer install $ ./vendor/bin/phpunit