F1.25 Laravel(Lumen) Security Step7: Configuration (Wpf, Xamarin, Angular SPA, Reactjs SPA) - chempkovsky/CS2WPF-and-CS2XAMARIN GitHub Wiki

1. Add Auth.php to the project

  • for the lumen project
    • copy vendor/laravel/lumen-framework/config/auth.php-file into the config-folder

picture

2. Open generated "app/Providers/AspnetuserViewAuthServiceProvider.php"

  • at the beginning of the file you will find what and how to configure
// ///////////////////////////////////////////////////////////////////////////////
// the generated code expects "config/database.php"-file to have the following config parameters
// ///////////////////////////////////////////////////////////////////////////////
//     ...
//     'connections' => [
//        ...
//
//        'aspnetforphpchckdbcontext' => [
//            'url' => 'mysql://root:[email protected]:3306/database_name_here?serverVersion=8.0&charset=utf8mb4',
//        ],
//        ...
// ///////////////////////////////////////////////////////////////////////////////
// the generated code expects "bootstrap/app.php"-file to have the following config parameters
// ///////////////////////////////////////////////////////////////////////////////
//
// ------------- ==1== uncomment the lines -------------
// $app->routeMiddleware([
//     'auth' => App\Http\Middleware\Authenticate::class,
// ]);
// ------------- ==1== end -------------
//
// ------------- ==2== uncomment the line -------------
// $app->register(App\Providers\AuthServiceProvider::class);
// ------------- ==2== End -------------
//
// ------------- ==3== add the line -------------
// $app->register(App\Providers\AspnetuserViewAuthServiceProvider::class);
// ------------- ==3== End -------------
//
// ------------- ==4== add the line -------------
// $app->configure('auth');
// ------------- ==4== End -------------
//
// ///////////////////////////////////////////////////////////////////////////////
// the generated code expects "config/auth.php"-file to have the following config parameters
// ///////////////////////////////////////////////////////////////////////////////
//
// return [
//
//    'defaults' => [
//        'guard' => env('AUTH_GUARD', 'aspnetsecurity'),
//    ],
//   
//
//    'guards' => [
//        'aspnetsecurity' => ['driver' => 'aspnetsecurityguard'],
//        'api' => ['driver' => 'api'],
//    ],
//
// ];
// ///////////////////////////////////////////////////////////////////////////////
// the generated code expects ".env"-file to have the following config parameters
// ///////////////////////////////////////////////////////////////////////////////
//     ...
//
//   AUTH_GUARD=aspnetsecurity
//   JWT_SECRET_KEY=SOME_SECRET_HERE 
//
// ///////////////////////////////////////////////////////////////////////////////
// To protect controllers using roles modify Middleware/Authenticate.php like bellow
// 
//
//    public function handle($request, Closure $next, $guard = null)
//    {
//       
//        if ($this->auth->guard($guard)->guest()) {
//            return response('Unauthorized.', 401);
//        }
//        $roles=null;
//        $user = $request->user();
//        if ($user !== null)
//          if($user instanceof GenericUser)
//             $roles= $user->roles;
//        switch( $request->path() ) {
//            case 'litgenreviewwebapi/addone':
//                if ($roles === null)
//                    return response('Unauthorized.', 401);
//                // check if current user has a required role (or roles)
//                break;
//            case 'litgenreviewwebapi/updateone':
//                if ($roles === null)
//                    return response('Unauthorized.', 401);
//                // check if current user has a required role (or roles)
//                break;
//            case 'litgenreviewwebapi/deleteone':
//                if ($roles === null)
//                    return response('Unauthorized.', 401);
//                // check if current user has a required role (or roles)
//                break;
//        }
//        return $next($request);
//    }
//
//  for the paths above routes/web.php must be defined as follows:
//
//  // protected methods
//  $router->group(['middleware' => 'auth:aspnetsecurity'], function () use ($router) {
//    $router->post('/litgenreviewwebapi/addone', ['uses' => 'LitGenreViewController@addone']);
//    $router->put('/litgenreviewwebapi/updateone', ['uses' => 'LitGenreViewController@updateone']);
//    $router->delete('/litgenreviewwebapi/deleteone', ['uses' => 'LitGenreViewController@deleteone']);
//  });
//  // unprotected methods
//  $router->group([], function () use ($router) {
//    $router->get('/litgenreviewwebapi/getall', ['uses' => 'LitGenreViewController@getall']);
//    $router->get('/litgenreviewwebapi/getwithfilter', ['uses' => 'LitGenreViewController@getwithfilter']);
//    $router->get('/litgenreviewwebapi/getone', ['uses' => 'LitGenreViewController@getone']);
//  });
//
// ///////////////////////////////////////////////////////////////////////////////

3.1. bootstrap/app.php-file settings

Here is a set of all changes:

$app->withFacades();
$app->configure('app');
$app->configure('database');
$app->configure('auth');
$app->routeMiddleware([
     'auth' => App\Http\Middleware\Authenticate::class,
 ]);
$app->register(App\Providers\AuthServiceProvider::class);
$app->register(App\Providers\AspnetuserViewAuthServiceProvider::class);

Reminder: We do not to uncomment the line

// $app->withEloquent();

-So App\Providers\AuthServiceProvider-class will not work correctly

3.2. config/auth.php-file settings

Here is a set of all changes:

    'defaults' => [
        'guard' => env('AUTH_GUARD', 'aspnetsecurity'),
    ],

    'guards' => [
        'aspnetsecurity' => ['driver' => 'aspnetsecurityguard'],
        'api' => ['driver' => 'api'],
    ],

4. config/database.php settings

Here is a set of all changes:

    'connections' => [
        ...
        'aspnetforphpchckdbcontext' => [
            'url' => 'mysql://root:[email protected]:3306/database_name_here?serverVersion=8.0&charset=utf8mb4',
        ],
        ...
    ];

5. .env settings

Here is a set of all changes:

JWT_SECRET_KEY=SOME_SECRET_KEY_HERE

JWT_SECRET_KEY is required inside boot()-method of the app/Providers/AspnetuserViewAuthServiceProvider.php

                $jwt = (array) JWT::decode(
                    $bearer, 
                    env('JWT_SECRET_KEY'),
                    ['HS256']
                );

6. Open generated "app/Http/Controllers/AspnetuserViewAuthController.php"

  • at the beginning of the file you will find what and how to configure
// ///////////////////////////////////////////////////////////////////////////////
// the generated code expects "config/database.php"-file to have the following config parameters
// ///////////////////////////////////////////////////////////////////////////////
//     ...
//     'connections' => [
//        ...
//
//        'aspnetforphpchckdbcontext' => [
//            'url' => 'mysql://root:[email protected]:3306/database_name_here?serverVersion=8.0&charset=utf8mb4',
//        ],
//        ...
// ///////////////////////////////////////////////////////////////////////////////
// Laravel: the generated code expects "routes/api.php"-file to have the following config parameters
// ///////////////////////////////////////////////////////////////////////////////
//        ...
// use App\Http\Controllers\AspnetuserViewAuthController;
//        ...
// Route::post('/api/Account/register', [AspnetuserViewAuthController::class,'register']);
// Route::post('/api/Account/changePassword', [AspnetuserViewAuthController::class,'changePassword']);
// Route::post('/api/Account/logout', [AspnetuserViewAuthController::class,'logout']);
// Route::post('/token', [AspnetuserViewAuthController::class,'token']);
//        ...
// ///////////////////////////////////////////////////////////////////////////////
// Lumen: the generated code expects "routes/web.php"-file to have the following config parameters
// ///////////////////////////////////////////////////////////////////////////////
//        ...
// 
//  //$router->group(['prefix' => 'api'], function () use ($router) {
//  $router->group([], function () use ($router) {
//   $router->post('/api/Account/register', ['uses' => 'AspnetuserViewAuthController@register']);
//   $router->post('/token', ['uses' => 'AspnetuserViewAuthController@token']);
//  });
//
//  $router->group(['middleware' => 'auth:aspnetsecurity'], function () use ($router) {
//   $router->post('/api/Account/changePassword', ['uses' => 'AspnetuserViewAuthController@changePassword']);
//   $router->post('/api/Account/logout', ['uses' => 'AspnetuserViewAuthController@logout']);
//  });
//
//        ...
// ///////////////////////////////////////////////////////////////////////////////
// the generated code expects ".env"-file to have the following config parameters
// ///////////////////////////////////////////////////////////////////////////////
//     ...
//
//   JWT_SECRET_KEY=SOME_SECRET_HERE 
// ///////////////////////////////////////////////////////////////////////////////

7. modify "routes/web.php" for Lumen-project

  $router->group([], function () use ($router) {
   $router->post('/api/Account/register', ['uses' => 'AspnetuserViewAuthController@register']);
   $router->post('/token', ['uses' => 'AspnetuserViewAuthController@token']);
  });

  $router->group(['middleware' => 'auth:aspnetsecurity'], function () use ($router) {
    $router->post('/api/Account/logout', ['uses' => 'AspnetuserViewAuthController@logout']);
    $router->post('/api/Account/changePassword', ['uses' => 'AspnetuserViewAuthController@changePassword']);
  });

This means that only authorized users can log out and change the password.

8. Role-bases protection: modify "handle()"-method of "app/Http/Middleware/Authenticate.php"

  • Open generated "app/Providers/AspnetuserViewAuthServiceProvider.php"-file again
    • at the beginning of the file you will find how to modify handle()-method of "app/Http/Middleware/Authenticate.php"-file to turn on Role-based protection:
    public function handle($request, Closure $next, $guard = null)
    {
       
        if ($this->auth->guard($guard)->guest()) {
            return response('Unauthorized.', 401);
        }
        $roles=null;
        $user = $request->user();
        if ($user !== null)
          if($user instanceof GenericUser)
             $roles= $user->roles;
        switch( $request->path() ) {
            case 'litgenreviewwebapi/addone':
                if ($roles === null)
                    return response('Unauthorized.', 401);
                // check if current user has a required role (or roles)
                break;
            case 'litgenreviewwebapi/updateone':
                if ($roles === null)
                    return response('Unauthorized.', 401);
                // check if current user has a required role (or roles)
                break;
            case 'litgenreviewwebapi/deleteone':
                if ($roles === null)
                    return response('Unauthorized.', 401);
                // check if current user has a required role (or roles)
                break;
        }
        return $next($request);
    }
  • In the sample code above we have three paths:
    • litgenreviewwebapi/addone
    • litgenreviewwebapi/updateone
    • litgenreviewwebapi/deleteone
  • To get ready role-protection for these paths we should define rotes in the "routes/web.php":
  $router->group(['middleware' => 'auth:aspnetsecurity'], function () use ($router) {
    $router->post('/litgenreviewwebapi/addone', ['uses' => 'LitGenreViewController@addone']);
    $router->put('/litgenreviewwebapi/updateone', ['uses' => 'LitGenreViewController@updateone']);
    $router->delete('/litgenreviewwebapi/deleteone', ['uses' => 'LitGenreViewController@deleteone']);
  });

if we want some routes to be available for unauthorized users we do not attach 'auth:aspnetsecurity'-middleware

$router->group([], function () use ($router) {
    $router->get('/litgenreviewwebapi/getall', ['uses' => 'LitGenreViewController@getall']);
    $router->get('/litgenreviewwebapi/getwithfilter', ['uses' => 'LitGenreViewController@getwithfilter']);
    $router->get('/litgenreviewwebapi/getone', ['uses' => 'LitGenreViewController@getone']);
  });

if we want some routes to be available for authorized users without any roles we attach 'auth:aspnetsecurity'-middleware but we do not check them in handle()-method of "app/Http/Middleware/Authenticate.php"-file

$router->group(['middleware' => 'auth:aspnetsecurity'], function () use ($router) {
    $router->get('/litgenreviewwebapi/getall', ['uses' => 'LitGenreViewController@getall']);
    $router->get('/litgenreviewwebapi/getwithfilter', ['uses' => 'LitGenreViewController@getwithfilter']);
    $router->get('/litgenreviewwebapi/getone', ['uses' => 'LitGenreViewController@getone']);
  });

9. App User with "roles"-field

  • For the Lumen project we use "GenericUser"-class for App User
    • Open generated "app/Providers/AspnetuserViewAuthServiceProvider.php"-file
      • find the code:
                return new GenericUser(
                    [
                        'Id' => $user->Id,
                        'Email'=> $user->Email,
                        'EmailConfirmed'=> $user->EmailConfirmed,
                        'PasswordHash'=> $user->PasswordHash,
                        'SecurityStamp'=> $user->SecurityStamp,
                        'PhoneNumber'=> $user->PhoneNumber,
                        'PhoneNumberConfirmed'=> $user->PhoneNumberConfirmed,
                        'TwoFactorEnabled'=> $user->TwoFactorEnabled,
                        'LockoutEndDateUtc'=> $user->LockoutEndDateUtc,
                        'LockoutEnabled'=> $user->LockoutEnabled,
                        'AccessFailedCount'=> $user->AccessFailedCount,
                        'UserName' => $user->UserName,
                        'roles' => $roles
                    ]
                );

10. Note:

  • The current implementation of role-based security is optimal
    • role check is done before starting any controller
    • bit-mask only sent once to client app right after login

11. modify routes/web.php for the generated controllers:

  • open each generated controller:
    • AspnetdashboardViewController.php
    • AspnetmodelViewController.php
    • AspnetrolemaskViewController.php
    • AspnetroleViewController.php
    • AspnetuserclaimViewController.php
    • AspnetuserloginViewController.php
    • AspnetusermaskViewController.php
    • AspnetuserrolesViewController.php
    • AspnetuserViewController.php
  • at the gebining of each file you will find "How to configure routes/web.php and config/database.php".
    • For instance, open the file AspnetdashboardViewController.php:
// ///////////////////////////////////////////////////////////////////////////////
// the generated code expects "config/database.php"-file to have the following config parameters
// ///////////////////////////////////////////////////////////////////////////////
//     ...
//     'connections' => [
//        ...
//
//        'aspnetforphpchckdbcontext' => [
//            'url' => 'mysql://root:[email protected]:3306/database_name_here?serverVersion=8.0&charset=utf8mb4',
//        ],
//        ...
// ///////////////////////////////////////////////////////////////////////////////
// Laravel: the generated code expects "routes/api.php"-file to have the following config parameters
// ///////////////////////////////////////////////////////////////////////////////
//        ...
// use App\Http\Controllers\AspnetdashboardViewController;
//        ...
// Route::get('/aspnetdashboardviewwebapi/getall', [AspnetdashboardViewController::class,'getall']);
// Route::get('/aspnetdashboardviewwebapi/getwithfilter', [AspnetdashboardViewController::class,'getwithfilter']);
// Route::get('/aspnetdashboardviewwebapi/getone', [AspnetdashboardViewController::class,'getone']);
// Route::post('/aspnetdashboardviewwebapi/addone', [AspnetdashboardViewController::class,'addone']);
// Route::put('/aspnetdashboardviewwebapi/updateone', [AspnetdashboardViewController::class,'updateone']);
// Route::delete('/aspnetdashboardviewwebapi/deleteone', [AspnetdashboardViewController::class,'deleteone']);
//        ...
// ///////////////////////////////////////////////////////////////////////////////
// Lumen: the generated code expects "routes/web.php"-file to have the following config parameters
// ///////////////////////////////////////////////////////////////////////////////
//        ...
// 
//  //$router->group(['prefix' => 'api'], function () use ($router) {
//  $router->group([], function () use ($router) {
//   $router->get('/aspnetdashboardviewwebapi/getall', ['uses' => 'AspnetdashboardViewController@getall']);
//   $router->get('/aspnetdashboardviewwebapi/getwithfilter', ['uses' => 'AspnetdashboardViewController@getwithfilter']);
//   $router->get('/aspnetdashboardviewwebapi/getone', ['uses' => 'AspnetdashboardViewController@getone']);
//   $router->post('/aspnetdashboardviewwebapi/addone', ['uses' => 'AspnetdashboardViewController@addone']);
//   $router->put('/aspnetdashboardviewwebapi/updateone', ['uses' => 'AspnetdashboardViewController@updateone']);
//   $router->delete('/aspnetdashboardviewwebapi/deleteone', ['uses' => 'AspnetdashboardViewController@deleteone']);
//  });
//        ...
// ///////////////////////////////////////////////////////////////////////////////

Note:

  • do not forget about middleware and handle()-method of "app/Http/Middleware/Authenticate.php"-file
Example for aspnetdashboardviewwebapi:
  • all nine Aspnet...ViewController.php controllers are for Admin user (user with admin role)
  • suppose the name of such a role will be ROLE_ADMIN
  • in the routes/web.php we add
  $router->group(['middleware' => 'auth:aspnetsecurity'], function () use ($router) {
    $router->get('/aspnetdashboardviewwebapi/getall', ['uses' => 'AspnetdashboardViewController@getall']);
    $router->get('/aspnetdashboardviewwebapi/getwithfilter', ['uses' => 'AspnetdashboardViewController@getwithfilter']);
    $router->get('/aspnetdashboardviewwebapi/getone', ['uses' => 'AspnetdashboardViewController@getone']);
    $router->post('/aspnetdashboardviewwebapi/addone', ['uses' => 'AspnetdashboardViewController@addone']);
    $router->put('/aspnetdashboardviewwebapi/updateone', ['uses' => 'AspnetdashboardViewController@updateone']);
    $router->delete('/aspnetdashboardviewwebapi/deleteone', ['uses' => 'AspnetdashboardViewController@deleteone']);
  });
  • we modify handle()-method of "app/Http/Middleware/Authenticate.php"-file as follows
    public function handle($request, Closure $next, $guard = null)
    {
       
        if ($this->auth->guard($guard)->guest()) {
            return response('Unauthorized.', 401);
        }
        $roles=null;
        $user = $request->user();
        if ($user !== null)
          if($user instanceof GenericUser)
             $roles= $user->roles;
        switch( $request->path() ) {
            case 'litgenreviewwebapi/addone':
                if ($roles === null)
                    return response('Unauthorized.', 401);
                // check if current user has a required role (or roles)
                break;
            case 'litgenreviewwebapi/updateone':
                if ($roles === null)
                    return response('Unauthorized.', 401);
                // check if current user has a required role (or roles)
                break;
            case 'litgenreviewwebapi/deleteone':
                if ($roles === null)
                    return response('Unauthorized.', 401);
                // check if current user has a required role (or roles)
                break;
            case 'aspnetdashboardviewwebapi/getall':
            case 'aspnetdashboardviewwebapi/getwithfilter':
            case 'aspnetdashboardviewwebapi/getone':
            case 'aspnetdashboardviewwebapi/addone':
            case 'aspnetdashboardviewwebapi/updateone':
            case 'aspnetdashboardviewwebapi/deleteone':
                if ($roles === null)
                    return response('Unauthorized.', 401);
                if (!in_array("ROLE_ADMIN", $roles)
                    return response('Unauthorized.', 401);
                break;
        }
        return $next($request);
    }