There is no license information available for the latest version (v0.1.2) of this package.

Facilitates authentication and authorization with Clearlink's OAuth2 implementation

v0.1.2 2017-04-21 21:33 UTC

README

Note that this package is not yet stable and the api is subject to change

This package is used to facilitate authentication with Clearlink's SSO and authorization via Clearlink's Permission microservice.

This package will also retreive a token on behalf of the client using OAuth2 client credentials grant type.

This version is only to be used in Laravel 5.4 projects. If your project is in Laravel 5.3, you will need to use the 1.* version instead.

Installation

Note that these instructions are for a Laravel installation and may not adequately describe the steps needed for a Lumen installation

First, require the package:

composer require clearlink/auth:2.*

Next add the following line to the projects config\app.php file in the 'providers' array:

/*
 * Package Service Providers...
 */
Clearlink\Auth\AuthServiceProvider::class,

In the projects .env file, add the following lines, replacing {{client_id}} and {{client_secret}} with their respective values for your application:

CL_SSO_CLIENT_ID={{client_id}}
CL_SSO_CLIENT_SECRET={{client_secret}}

Finally, you will need to create a file in storage/app/ called cl_auth_public.pem. The contents for this file is the public key set in the SSO application configuration.

Configuration

This package handles both authentication and authorization. You can choose to not use the authorization features, but you cannot use authorization without also using the authentication features.

Authentication Configuration

The first thing that you will want to do is publish the package configuration file. This is done by running:

php artisan vendor:publish --tag=clearlink-auth

In the projects /config directory, you will find a file called clearlink-auth.php. You will need to modify the values for 'auth-endpoint', 'token-endpoint', and 'permission-endpoint' to reflect the correct URL for your environment.

Next, modify the app\Exceptions\Handler->unauthenticated function to redirect to SSO in the case of not being authenticated:

protected function unauthenticated($request, AuthenticationException $exception)
{
    if ($request->expectsJson()) {
        return response()->json(['error' => 'Unauthenticated.'], 401);
    }

    //replace this line
    //return redirect()->guest('login');
    //with this line
    return app(AuthService::class)->returnAuthorizationCodeRedirect($request);
}

Finally, you need to set the driver options in the projects config/auth.php file to use the drivers supplied by this package. Simply prepend 'clearlink-' to both of the drivers for the api and web authentication guards, and change the driver for the users provider to 'clearlink':

'guards' => [
    'web' => [
        'driver' => 'clearlink-session',
        'provider' => 'users',
    ],

    'api' => [
        'driver' => 'clearlink-token',
        'provider' => 'users',
    ],
],

'providers' => [
    'users' => [
        'driver' => 'clearlink',
    ],
],

Now, whenever you call $request->user() or Auth::user(), you will get an instance of Clearlink\Auth\User. This class has the properties of:

public $resouce_owner_id;
public $name;
public $username;

Finally, you need to add the auth middleware to the routes that you want to protect. Please see the Laravel documentation for adding middleware to routes.

Post authentication

If you need to do any additional setup for the user after they are authenticated, you can call the following function in the boot function in the application's AppServiceProvider: :

AuthService::setAuthenticatedFunction($func);

This function will allow you to perform any actions for the user prior to the authentication middleware returning and prior to the retrieval of a custom user object. This function takes a function with the following signature as it's argument:

/**
 * @param Clearlink\Auth\User $user
 */
function (User $user)

This function is useful if you are using a local users table and need to ensure that the user is created.

Using local user model

You need to ensure that the object you are using implements Illuminate\Contracts\Auth\Authenticatable.

If your application requires a different user model then the one supplied, you can easily configure the package to use that instead of the default model.

First, you need to add the ClearlinkAuthUser trait to the model that you want to use. Take the default \App\User model as an example:

namespace App;

use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;

//Add this line
use Clearlink\Auth\Traits\ClearlinkAuthUser;

class User extends Authenticatable
{
    //Add the trait to the class
    use Notifiable, ClearlinkAuthUser;

    //Omitted for brevity
}

Then open /config/clearlink-auth.php. At the bottom of the array, there is a key called 'user-model' that is set to null. All that you need to do to use a different user model is set this to an instance of the model that you want to use:

'user-model' => new \App\User()

You can set this to any object that has a function with the signature:

/**
 * @param int $resource_owner_id
 *
 * @return Illuminate\Contracts\Auth\Authenticatable | null
 */
public function find($resource_owner_id);

By default, objects extending from Illuminate\Database\Eloquent\Model implement this function.

The package will now return an instance of the supplied model.

Authorization Configuration

Remember that the Authorization features require the Authentication features to be configured and enabled.

How authorization works

The authorization features are designed to be implemented as a middleware and use the route's name as the required permission name. By default, all routes are denied unless the user has the necessary permission or the default permission check is disabled.

As an example, lets look at some routes from the Permissions microservice:

ID  NAME                        METHOD  ROUTE
--  -------------------------   ------  -------------------------
1   management.groups.index     GET     /management/groups/
2   management.groups.store     POST    /management/groups/
3   management.groups.show      GET     /management/groups/{index}
4   management.groups.update    PUT     /management/groups/{index}
5   management.groups.destroy   DELETE  /management/groups/{index}

Given the above routes, if you wanted to access route 1, you would need the permission: management.groups.index.

When creating permissions, you do not need to worry about conflicts with other applications as all permissions are scoped to a single application.

This authorization package also allows access to routes if they have a permission that is the superset of the requested route. Again, using route 1, the authenticated user would have access to that route if they had any of the following permissions:

management.
management.groups.
management.groups.index

This allows for easier assignment of permissions, but still allows for exlucing certain routes from the application users.

It is important when creating permissions that the permission ends with a '.' if the route has sub routes. Using the above example, if the user had the permission management.g, they would also be allowed access to any routes that have a name starting with management.g, such as management.get-something-dangerous. This can cause undesired access to users.

Currently, this package and the Permission microservice do not support DENY permissions, only ALLOW. So, again using the above routes, if you wanted your users to be able to invoke every route but route 5, the user would need the permissions for the individual routes, and not have a superset permission:

management.groups.index
management.groups.store
management.groups.show
management.groups.update
Authorization versus Configuration

It is important to note that this package is not intended for configuration. Using route 3 from above:

ID  NAME                        METHOD  ROUTE
--  -------------------------   ------  -------------------------
3   management.groups.show      GET     /management/groups/{index}

this package will not enforce any restrictions on what value is supplied for {index}. This package would enforce if you can see any groups, not what groups you can see. If you need to do any restrictions at a higer level then the route, that needs to be handled via configuration, and that is outside of this package's scope.

As a general rule of thumb, authorization enforces if you can access a type resource while configuration enforces what resources are available within that type.

Implementation

To enable the permissions middleware, add the following line to the $routeMiddleware array in app/Http/Kernal.php:

'permissions' => \Clearlink\Auth\Middleware\Permissions::class,

After that, all that is required to use the default permission check (using the route name), is to add the middleware to your application routes. Please see the Laravel documentation for instructions on adding middleware to your routes.

Disabling default check

If you do not want to have the default check ran, you can disable it by adding the following line to the boot function in the application's AppServiceProvider:

PermissionService::disableDefaultCheck();

Adding additional checks

In some situations, you may want to specify different behaviors for how authorization is granted in your application. One example would be where you have the following routes:

WEB
NAME            METHOD  ROUTE
--------------  ------  -----------------
archives.index  GET     /archives/
archives.show   GET     /archives/{index}

API
NAME                METHOD  ROUTE
--------------      ------  -----------------
api.archives.index  GET     /api/archives/
api.archives.show   GET     /api/archives/{index}

In this example, the frontend of your application communicates with the backend via api calls instead of form submissions. Given this, if you wanted your user to be able to access /archives/, they would need the following permissions:

archives.index
api.archives.index

The first is needed to display the web page, but the second is needed to get the data to populate the web page. This can get cumbersome for large applications, so you could instead include a new rule that will ignore the api. prefix of the route name if it exists:

//In AppServiceProvider->boot()

PermissionService::disableDefaultCheck();
PermissionService::addCheck(function($route_name, $permission, $request){
    $offset = 0;
    if(strpos($route_name, 'api.') === 0)
        $offset = 4; //api. is 4 characters long

    return strpos($route_name, $permission, $offset) === $offset;
});

This will instead cause the package to use your rule and not use the default rule provided.

You can add as many rules as you would like, and you can use them in conjunction with the default rule. When using multiple rules, if any of the rules return TRUE, the user will be allowed access.

The signature for the function supplied to PermissionService::addCheck() is:

/**
 *
 * @param string $route_name
 * @param string $permission
 * @param Illuminate\Http\Request $request
 *
 * @return bool
 */
function ($route_name, $permission, Request $request)