How to enable both api and web guard in laravel
Asked Answered
P

7

14
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.

Pence answered 1/2, 2019 at 10:19 Comment(15)
Not sure what the use case is here.Gunrunning
@Gunrunning okay, let me try to explain my problem, if I will use api guard, I will be unable to login through web app and if I will use web guard non of api end point will work, how I can enable both?Pence
You could use auth:api,web as middleware where you would usually have auth as middleware to have the authentication check work with both.Gunrunning
@apokryfos, so I need to use auth:api,web in both web.php and api.php routes?Pence
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 strategyGunrunning
@Gunrunning I tried, but it is getting redirected to login page again.Pence
@Gunrunning what would be the best way to work with both web and api?Pence
@Gunrunning Also let me know, if I need to share anything more?Pence
Can you please show your web.php and api.php routes @PrafullaKumarSahuCommunal
@LimKeanPhang I have edited the question, please check it.Pence
So web.php is working while api.php not working? @PrafullaKumarSahuCommunal
@LimKeanPhang yes, now I can enable any one of them.Pence
@PrafullaKumarSahu i've made slight adjustment and added the namespace in api route, i'll recommend using postman to test the API see if its workCommunal
@Gunrunning I have updated my question, I hope this makes little clearer.Pence
Let us continue this discussion in chat.Pence
P
13

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');
});
Pence answered 4/2, 2019 at 9:26 Comment(4)
thanks, it helps me a lot. Only set specific guard help me solve problemRecite
@MayWeatherVN I am glad, it helped you.Pence
If I understand correctly, you leave the default guard unchanged as web (in config/auth.php file) and because of that it should be specified manually like ->guard('api') for API. But why is refresh() missing api guard?Hemophilia
in the function respondWithToken, you should also add ->guard('api') after auth(). You should be able to do this: 'expires_in' => mouth()->guard('api')->factory()->getTTL() * 60Nata
C
2

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',
    ],
],
Communal answered 1/2, 2019 at 12:48 Comment(29)
I am getting Uncaught Symfony\Component\HttpKernel\Exception\NotFoundHttpException in for web. I want both of them to work.Pence
I've updated the code and make the structure more clearer please try againCommunal
I am getting Method Illuminate\\Auth\\SessionGuard::factory does not exist." it will work if I will change it the default guard to api. but then web will not work.Pence
You need to both guard not by just adding auth|api refer to the latest updateCommunal
I have added my auth.php pleasae have a look at it, I am already adding both guard there.Pence
I'll be better if you separate it..i dont think i'll work that way as the driver might be differentCommunal
Then, How I can allow both web application and api work? Could you please suggest any idea as most of the web part and api end points are already coded.Pence
I've updated the config/auth.php from my answer please checkCommunal
That will only allow web not the api. check this not different from the auth.php I have recently added to my question.Pence
Have you tried to get the login page working from api? try return dd('123') from controller and make sure your URL is correctCommunal
Both are working separably, if I will set default guard to api, api is working and if setting is web, web is working.Pence
There no need to modify the default guard. did you add localhost/api/auth/.. when you call for API?Communal
Does it return anything? Can you dd something from the login function or there's error. Reset the default btw. Are you using any jtw plugin or you might have miss configuration over thereCommunal
I am getting Method Illuminate\\Auth\\SessionGuard::factory does not exist. this error.Pence
Auth Controller please. construct__ functionCommunal
` public function __construct() { $this->middleware('auth:api', ['except' => ['login', 'register']]); }`Pence
public function __construct() { $this->middleware('guest')->except('logout'); } Your login controller modified?Communal
Let us continue this discussion in chat.Pence
I am still getting Method Illuminate\\Auth\\SessionGuard::factory does not exist.Pence
no, I think this is jwt expire time, that has no relation to my problem. Still I will try now and let you know.Pence
csrf token on form?Communal
Also checked the changes described in git issue, that is okay, that will not solve this problem. It is interesting no body has faced this situation, may be becasue everyone is using api and some js framework, not blade, jquery for web app, but in our case we were asked to use jquery and blade and we did not think we need to do everything through api only, we thought there will be a way to make work both, actually I still believe there must be a way.Pence
Hey, I got something, but in this case I am not getting the authentication token, which I will need. Let me update the question with the change and the output.Pence
So the API and Web both are working already? Bearer is a token you get once authenticatedCommunal
yes both are working, but after authentication, I am not getting the token.Pence
i think its better you open a new post cause the comment over here is overloaded and you have your question answered. Just that there's a new problemCommunal
if I will not get the access token, people will never be able to use api, so the problem is not solved, I am trying few things, if I will get a solution, I will post it as answer, yes, we should actually go to chat.Pence
Probably some configuration missing while setting up from those package. Just check the overall package of jwt will do. I'm actually using laravel passport instead of jwt so i'm cant provide much help for now~Communal
I have added an answer, please have a look, that is working fine, but still having a problem. We can discuss in chat or later I will post another question on that, if I could not solve it.Pence
T
2

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
}
Tasset answered 11/10, 2020 at 10:22 Comment(0)
M
1

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.

Matrimony answered 21/12, 2019 at 0:59 Comment(0)
L
1

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,
Lastditch answered 19/8, 2022 at 15:43 Comment(0)
E
0

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.

Enrique answered 22/1, 2020 at 11:11 Comment(1)
Please check the already implemented logic and it is working, so it is verified. Welcome to SO.Pence
L
0

I want to add a corrected answer, in Larevel 11 you would like to change

    {
        $this->middleware('auth:api', ['except' => ['login']]);
    }

To this:

public static function middleware(): array
    {
        return [
            new Middleware('auth:api', except: ['login'])
        ];
    }
Littman answered 11/7 at 8:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.