How to enable both api and web guard in laravel

25.1k Views Asked by At
Laravel 5.7

PHP 7.2.10

Currently I am able to use any one of web and api guards, is there any way to allow both, so that both web app and api will work together.

Something like

return [

    /*
    |--------------------------------------------------------------------------
    | Authentication Defaults
    |--------------------------------------------------------------------------
    |
    | This option controls the default authentication "guard" and password
    | reset options for your application. You may change these defaults
    | as required, but they're a perfect start for most applications.
    |
    */

    'defaults' => [
        'guard' => 'api|web',
        'passwords' => 'users',
    ],

with out using schema, here is a solution/workaround which needs changes in schema, what I will not prefer. Also I do not need access token for registration, what this answer is doing.

api.php

Route::group([
    'middleware' => 'api|web',
    'prefix' => 'auth'
], function ($router) {

   Route::post('register', 'Auth\AuthController@register')->name('api.register');
    Route::post('forgot-password', 'Auth\ForgotPasswordController@forgotPassword')->name('api.forgot-password');
    Route::post('login', 'Auth\AuthController@login')->name('api.login');
    Route::middleware('auth')->post('logout', 'Auth\AuthController@logout')->name('api.logout');

web.php

Auth::routes(['verify' => true]);
Route::prefix('admin')->group(function () {
 Route::middleware('auth', 'permission:super-admin|association-member')->resource('users', 'Auth\UserController');
});

config/auth.php

return [

    /*
    |--------------------------------------------------------------------------
    | Authentication Defaults
    |--------------------------------------------------------------------------
    |
    | This option controls the default authentication "guard" and password
    | reset options for your application. You may change these defaults
    | as required, but they're a perfect start for most applications.
    |
    */

    'defaults' => [
        'guard' => 'web', //api
        'passwords' => 'users',
    ],

    /*
    |--------------------------------------------------------------------------
    | Authentication Guards
    |--------------------------------------------------------------------------
    |
    | Next, you may define every authentication guard for your application.
    | Of course, a great default configuration has been defined for you
    | here which uses session storage and the Eloquent user provider.
    |
    | All authentication drivers have a user provider. This defines how the
    | users are actually retrieved out of your database or other storage
    | mechanisms used by this application to persist your user's data.
    |
    | Supported: "session", "token"
    |
    */

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

        'api' => [
            'driver' => 'jwt',
            'provider' => 'users',
        ],
    ],

Update As @apokryfos said, If you want both to work for both then yes. However, I think that's bad practice. API routes should only allow API authentication since web authentication usually uses the session which API routes don't use anyway. If I were you I'd take a step back and rethink my entire strategy.

I too do not want to make both work for both, I just want to make work both api and web app parallelly, now I am able to use any one of them.

Update2 As @Lim Kean Phang suggested the git issue link

I changed

  protected function respondWithToken($token)
    {
        return response()->json([
            'access_token' => $token,
            'token_type' => 'bearer',
            'expires_in' =>  auth('api')->factory()->getTTL() * 60,//auth()->factory()->getTTL() * 60,
            'status' => 200,
            "response" => "Successfully login",
        ]);
    }

The expires_in value, but now I am not getting the access token.

The api response is

{
    "access_token": true,
    "token_type": "bearer",
    "expires_in": 31536000,
    "status": 200,
    "response": "Successfully login"
}

Update 3 Added a github issue as could not find any possible solution to make it work.

6

There are 6 best solutions below

4
On BEST ANSWER

I changed the AuthController to something like

<?php

namespace App\Http\Controllers;

use Auth;
use Illuminate\Http\Request;

class AuthController extends Controller
{
    /**
     * Create a new AuthController instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('auth:api', ['except' => ['login']]);
    }

    /**
     * Get a JWT via given credentials.
     *
     * @return \Illuminate\Http\JsonResponse
     */
    public function login()
    {
        $credentials = request(['username', 'password']);

        $token = auth()->guard('api')->attempt($credentials);

        if (!$token) {
            return response()->json(['error' => 'Unauthorized'], 401);
        }

        return $this->respondWithToken($token);
    }

    /**
     * Log the user out (Invalidate the token).
     *
     * @return \Illuminate\Http\JsonResponse
     */
    public function logout()
    {
        auth()->guard('api')->logout();

        return response()->json(['message' => 'Successfully logged out']);
    }

    /**
     * Refresh a token.
     *
     * @return \Illuminate\Http\JsonResponse
     */
    public function refresh()
    {
        return $this->respondWithToken(auth()->refresh());
    }

    /**
     * Get the token array structure.
     *
     * @param string $token
     *
     * @return \Illuminate\Http\JsonResponse
     */
    protected function respondWithToken($token)
    {
        return response()->json([
            'access_token' => $token,
            'token_type'   => 'bearer',
            'expires_in'   => auth('api')->factory()->getTTL() * 60,
        ]);
    }
}

And in api.php changing auth to jwt.auth solves the problem.

Route::group([
    'middleware' => 'api',
    'prefix' => 'auth'
], function ($router) {

    Route::post('register', 'Auth\AuthController@register')->name('api.register');
    Route::post('forgot-password', 'Auth\ForgotPasswordController@forgotPassword')->name('api.forgot-password');
    Route::post('login', 'Auth\AuthController@login')->name('api.login');
    Route::middleware('jwt.auth')->post('logout', 'Auth\AuthController@logout')->name('api.logout');
    Route::middleware('auth')->post('refresh', 'Auth\AuthController@refresh')->name('api.refresh');
    Route::middleware('jwt.auth')->post('me', 'Auth\AuthController@me')->name('api.me');
});
0
On

setting the default driver in the constructor also works:

public function __construct() {
   auth()->setDefaultDriver('api');
}

and if you've configured everything correctly then calling Auth::guard() should return Tymon\JWTAuth\JWTGuard

alternatively you can also pass the driver as param to guard method like this:

private method guard() {
   return Auth::guard('api'); // Tymon\JWTAuth\JWTGuard
}
0
On

I just came across this issue myself, and post my answer in case anyone finds it useful.

In my case I need my API to be accessible both by my website and by external clients. The website uses the session guard, because the browser automatically includes any session cookie with each request. The other clients use the api guard with the token driver, because they don't handle cookies, but use the token_id field in the users table.

// contig/auth.php
'guards' => [
    'web' => [
        'driver' => 'session',
        'provider' => 'users',
    ],
    'api' => [
        'driver' => 'token',
        'provider' => 'users',
    ],
],

Now I can protect my routes like this:

// routes/api.php
Route::group(['middleware' => ['auth:web,api']], function () {
    Route::get('/abc', 'MyController@abc');
});

Note the web,api syntax supported out of the box by Laravel, but undocumented. I realized this is possible by analyzing Laravel's source code.

0
On

A solution for me was:

  1. Take off the $guard as a parameter on return for the handle in Middleware Authenticate:
app/Htpp/Middleware/Authenticate.php

 public function handle($request, Closure $next, ...$guards)
    {
        //your code

        return parent::handle($request, $next);
    }

  1. You must set Driver api as default in your request.
auth()->setDefaultDriver('api');

For this, you can set on construct in each controller, or create a middleware to use in your rotes I created a Middleware with name SetDriverGuardApi

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;

class SetDriverGuardApi
{
    /**
     * Handle an incoming request.
     *
     * @param Request $request
     * @param Closure $next
     * @return mixed
     */
    public function handle(Request $request, Closure $next)
    {
        auth()->setDefaultDriver('api');
        return $next($request);
    }
}

and I did register the Middleware on Kernel for the api's group, in first line

        'api' => [
            \App\Http\Middleware\SetDriverGuardApi::class,
29
On

API route, you should use postman from chrome/app to test the API

Route::group(['prefix' => 'auth',namespace =>'App\Http\Controller'], function () {
    Route::post('login', 'Auth\AuthController@login')->name('api.login');

    Route::group(['middleware' => 'auth:api'], function () {

       Route::post('register', 'Auth\AuthController@register')->name('api.register');
        Route::post('forgot-password', 'Auth\ForgotPasswordController@forgotPassword')->name('api.forgot-password');
        Route::post('logout', 'Auth\AuthController@logout')->name('api.logout');
});
});

Config/auth.php

return [

/*
|--------------------------------------------------------------------------
| Authentication Defaults
|--------------------------------------------------------------------------
|
| This option controls the default authentication "guard" and password
| reset options for your application. You may change these defaults
| as required, but they're a perfect start for most applications.
|
*/

'defaults' => [
    'guard' => 'web',
    'passwords' => 'users',
],

/*
|--------------------------------------------------------------------------
| Authentication Guards
|--------------------------------------------------------------------------
|
| Next, you may define every authentication guard for your application.
| Of course, a great default configuration has been defined for you
| here which uses session storage and the Eloquent user provider.
|
| All authentication drivers have a user provider. This defines how the
| users are actually retrieved out of your database or other storage
| mechanisms used by this application to persist your user's data.
|
| Supported: "session", "token"
|
*/

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

    'api' => [
        'driver' => 'jwt',
        'provider' => 'users',
    ],
],
1
On

You can not use both guard (web & api) at same time so what you have to do is generate the jwt using JWTAuth::attempt function like below.

  1. add below code at the top of the AuthController
use JWTAuth;
  1. update login function with below code.
    public function login(){
            $credentials = request(['email', 'password']);

            if (! $token = JWTAuth::attempt($credentials)) {
                return response()->json(['error' => 'Unauthorized'], 401);
                }
                return response()->json(['status'=>200,'token'=>$token]);
    }
  1. Make sure in auth.php you are using web guard.

This will generate the token and you will be able to use both the auth guards.