javaabu/efaas-socialite

eFaas Provider for Laravel Socialite

v3.5.1 2024-07-02 07:45 UTC

README

Latest Version on Packagist Test Status Code Coverage Badge Total Downloads

Laravel Socialite Provider for eFaas.

Note: Current version of this package is based on eFaas Documentation version 2.2

Requirements

This package requires the following:

  • Laravel 6.0 or higher
  • PHP 7.4 or higher
  • ext-openssl PHP extension

Installation

For Laravel 6.0+, you can install the package via composer:

composer require javaabu/efaas-socialite

For Laravel 5.6, use version 1.x

composer require javaabu/efaas-socialite:^1.0

Add configuration to your .env file

Add the following config to your .env file

EFAAS_CLIENT_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
EFAAS_CLIENT_SECRET=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
EFAAS_REDIRECT_URI=https://your-app.com/path/to/efaas/callback
EFAAS_MODE=development

# for production use
#EFAAS_MODE=production

Publishing the config file

Optionally you can also publish the config file to config/efaas.php:

php artisan vendor:publish --provider="Javaabu\EfaasSocialite\Providers\EfaasSocialiteServiceProvider" --tag="efaas-config"

This is the default content of the config file:

<?php

return [

    /**
     * eFaas client config
     */
    'client' => [
        /**
         * eFaas Client ID
         */
        'client_id' => env('EFAAS_CLIENT_ID'),

        /**
         * eFaas Client Secret
         */
        'client_secret' => env('EFAAS_CLIENT_SECRET'),

        /**
         * eFaas Redirect url
         */
        'redirect' => env('EFAAS_REDIRECT_URI'),

        /**
         * Development mode
         * supports "production" and "development"
         */
        'mode' => env('EFAAS_MODE', 'development'),

        /**
         * Default scopes for the eFaas client
         */
        'scopes' => [
            'openid',
            'efaas.profile',
            'efaas.birthdate',
            'efaas.email',
            'efaas.mobile',
            'efaas.photo',
            'efaas.permanent_address',
            'efaas.country',
            'efaas.passport_number',
            'efaas.work_permit_status'
        ],
    ],

    /*
     * This model will be used to store efaas session sids
     * The class must implement \Javaabu\EfaasSocialite\Contracts\EfaasSessionContract
     */
    'session_model' => \Javaabu\EfaasSocialite\Models\EfaasSession::class,

    /*
     * This handler will be used to manage saving and destroying efaas session records
     * The class must implement \Javaabu\EfaasSocialite\Contracts\EfaasSessionHandlerContract
     */
    'session_handler' => \Javaabu\EfaasSocialite\EfaasSessionHandler::class,

    /*
     * This is the name of the table that will be created by the migration and
     * used by the EfaasSession model shipped with this package.
     */
    'table_name' => 'efaas_sessions',

    /*
     * This is the database connection that will be used by the migration and
     * the EfaasSession model shipped with this package. In case it's not set
     * Laravel's database.default will be used instead.
     */
    'database_connection' => env('EFAAS_SESSIONS_DB_CONNECTION'),
];

Publishing migrations

This package ships with the migrations for an efaas_sessions table which can be used to implement back channel logout. You can publish these migrations using the following Artisan command:

php artisan vendor:publish --provider="Javaabu\EfaasSocialite\Providers\EfaasSocialiteServiceProvider" --tag="efaas-migrations"

After publishing the migrations, you can run them:

php artisan migrate

Usage

Note: A demo implementation of this package is available here.

You should now be able to use the provider like you would regularly use Socialite (assuming you have the facade installed): Refer to the Official Social Docs for more info.

Warning: If you get 403 Forbidden error when your Laravel app makes requests to the eFaas authorization endpoints, request NCIT to whitelist your server IP.

return Socialite::driver('efaas')->redirect();

and in your callback handler, you can access the user data like so. Remember to save the user's id_token and sid (session id).

$efaas_user = Socialite::driver('efaas')->user();
$id_token = $efaas_user->id_token;
$sid = $efaas_user->sid;

session()->put('efaas_id_token', $id_token);
session()->put('efaas_sid', $sid);

Enabling PKCE

By default, this package has PKCE disabled. To enable PKCE, use the enablePKCE() method in both your redirect call and the callback handler.

return Socialite::driver('efaas')->enablePKCE()->redirect();
// inside callback handler
$efaas_user = Socialite::driver('efaas')->enablePKCE()->user();

Logging out the eFaas User

In your Laravel logout redirect, redirect with the provider logOut() method using the id token saved during login

$id_token = session('id_token');
return Socialite::driver('efaas')->logOut($id_token, $post_logout_redirect_url);

Note: Since the id_token can be very long, you might run into nginx errors when redirecting. To fix this you can add the following to your nginx config. More info here.

fastcgi_buffers 16 16k;
fastcgi_buffer_size 32k;

Using eFaas One-tap Login

This package will automatically add an /efaas-one-tap-login endpoint to your web routes which will redirect to eFaas with the eFaas login code.

Sometimes you may wish to customize the routes defined by the Efaas Provider. To achieve this, you first need to ignore the routes registered by Efaas Provider by adding EfaasProvider::ignoreRoutes to the register method of your application's AppServiceProvider:

use Javaabu\EfaasSocialite\EfaasProvider;

/**
 * Register any application services.
 */
public function register(): void
{
    EfaasProvider::ignoreRoutes();
}

Then, you may copy the routes defined by Efaas Provider in its routes file to your application's routes/web.php file and modify them to your liking:

Route::group([
    'as' => 'efaas.',
    'namespace' => '\Javaabu\EfaasSocialite\Http\Controllers',
], function () {
    // Efaas routes...
});

Implementing Front Channel Single Sign Out

First, during login, in your efaas callback handler method, save the users sid (session ID) to your session.

$efaas_user = Socialite::driver('efaas')->user();
$sid = $efaas_user->sid;

session()->put('efaas_sid', $sid);

Then, in your single sign out controller handler method, first retrieve the logout token's sid using the eFaas provider's getLogoutSid() method. The method will return null if the provided logout token is invalid. You can then compare the saved sid in your current session with the retrieved sid and logout the user if they match.

...
public function handleFrontChannelSingleSignOut(Request $request)
{
    $saved_sid = session('efaas_sid');
    $request_sid = Socialite::driver('efaas')->getLogoutSid();
    
    if ($request_sid && $saved_sid == $request_sid) {
        // the logout session matches your saved sid
        // logout your user here
        auth()->guard('web')->logout();
        
        $request->session()->invalidate();
        $request->session()->regenerateToken();
    }     
      
    return redirect()->to('/your-redirect-url')        
}
...

Implementing Back Channel Single Sign Out

For Back Channel Logout, you will need to use Laravel's database session driver and the provided efaas_sessions migration.

During login, save the user's sid (session ID) using the eFaas provider's sessionHandler():

$efaas_user = Socialite::driver('efaas')->user();
$sid = $efaas_user->sid;

Socialite::driver('efaas')
    ->sessionHandler()
    ->saveSid($sid);

Then, in your single sign out controller handler method, first retrieve the logout token's sid using the eFaas provider's getLogoutSid() method. The method will return null if the provided logout token is invalid. You can then use the eFaas provider's sessionHandler() to logout all laravel sessions that match the sid.

...
public function handleBackChannelSingleSignOut(Request $request)
{    
    $sid = Socialite::driver('efaas')->getLogoutSid();
    
    if ($sid) {
        Socialite::driver('efaas')
            ->sessionHandler()
            ->logoutSessions($sid);
    }
    
    // for back channel logout you must return 200 OK response
    return response()->json([
        'success' => ! empty($request_sid)  
    ]);    
}
...

Authenticating from mobile apps

To authenticate users from mobile apps, redirect to the eFaas login screen through a Web View on the mobile app. Then intercept the code (authorization code) from eFaas after they redirect you back to your website after logging in to eFaas.

Once your mobile app receives the auth code, send the code to your API endpoint. You can then get the eFaas user details from your server side using the auth code as follows. Remember to use the stateless() option as the redirect had originated outside of your server:

$efaas_user = Socialite::driver('efaas')->stateless()->userFromCode($code);

After you receive the eFaas user, you can then issue your own access token or API key according to whatever authentication scheme you use for your API.

Changing the eFaas login prompt behaviour

The eFaas login prompt behaviour can be customized by modifying the prompt option on your redirect request

return Socialite::driver('efaas')->with(['prompt' => 'select_account'])->redirect();

The available prompt options are:

Available Methods for eFaas Provider

$provider = Socialite::driver('efaas');

$provider->parseJWT($token); // Parses a JWT token string into a Lcobucci\JWT\Token
$provider->getSidFromToken($token); // Validates a given JWT token and returns the sid from the token
$provider->getJwksResponse(false); // Returns the JWKs (JSON Web Keys) response as an array from the eFaas API. Optionally return the response as a json string using the optional boolean argument
$provider->getPublicKey('5CDA5CF378397733DD33EFBDA82D0F317DCC1D53RS256'); // Returns the public key from JWKs for the given key id as a PEM key string  

Available Methods and Public Properties for eFaas User

$efaas_user->isMaldivian(); // Check if is a Maldivian
$efaas_user->getDhivehiName(); // Full name in Dhivehi
$efaas_user->getPhotoMimetype(); // Get the mimetype of the user photo
$efaas_user->getPhotoExtension(); // Get the file extension of the user photo
$efaas_user->getPhotoBase64(); // Get the user photo as a base64 encoded string
$efaas_user->getPhotoDataUrl(); // Get the user photo as a data url
$efaas_user->savePhoto('photo', './path/to/save'); // Saves the user photo to ./path/to/save/photo.jpg and returns the full file path
$efaas_user->getAvatar(); // Alias of getPhotoBase64()
$efaas_user->sid; // Session id of the user
$efaas_user->id_token; // ID Token of the user
$efaas_user->token; // Access token of the user

Changing the eFaas request scopes

By default, this package adds all available scopes to the eFaas redirect. You can modify the default scopes by publishing the package config file and changing the scopes. To customize the scopes on a per request basis, you can override the scopes during the redirect.

return Socialite::driver('efaas')->setScopes(['efaas.openid', 'efaas.profile'])->redirect();

Getting eFaas data from eFaas User object

$id_number = $oauth_user->idnumber;

Available eFaas data fields

Different data is associated with different scopes. By default, all scopes are included, so you should be able to get all the data fields.

Scope: efaas.openid
Scope: efaas.profile
Scope: efaas.email
Scope: efaas.mobile
Scope: efaas.birthdate
Scope: efaas.photo
Scope: efaas.work_permit_status
Scope: efaas.passport_number
Scope: efaas.country
Scope: efaas.permanent_address

Here are the fields of the EfaasAddress object:

The EfaasAddress class also has the following methods:

$permanent_address = $efaas_user->permanent_address;

$permanent_address->getFormattedAddress(); // Get the address with the ward abbreviation. eg: M. Blue Light
$permanent_address->getDhivehiFormattedAddress(); // Get the address in Dhivehi with the ward abbreviation. eg: މ. ބުލޫ ލައިޓް

Testing

You can run the tests with

./vendor/bin/phpunit

Changelog

Please see CHANGELOG for more information what has changed recently.

Contributing

Please see CONTRIBUTING for details.

Security

If you discover any security related issues, please email info@javaabu.com instead of using the issue tracker.

Credits

License

The MIT License (MIT). Please see License File for more information.