laravel / passport

Laravel Passport provides OAuth2 server support to Laravel.
https://laravel.com/docs/passport
MIT License
3.28k stars 777 forks source link

CreateFreshApiToken don't create the laravel_token cookie #400

Closed tobecwb closed 5 years ago

tobecwb commented 7 years ago

I don't known if this is the best place for this, but I'm struggling with this for two days, almost giving up!

So, following up the "Consuming Your API With JavaScript" section of documentation, I added the \Laravel\Passport\Http\Middleware\CreateFreshApiToken::class in App\Http\Kernel.php (in web), and authenticated my user with axios.

window.authorize = function() {
    let data = {
        grant_type: 'password',
        client_id: 2,
        client_secret: 'fNoSz3LnZYnHE1MHgUV3FlJ3gWEpACd5gCvJEW2p',
        username: 'abc@def.com',
        password: 'admin',
        scope: '',
    };

    window.axios.post('/oauth/token', data).then(r => {
        if (r.status === 200) {
            console.log('got a valid response');
            console.log(r.data);
        } else {
            console.log(r.status);
        }
    }).catch(r => {
        console.error(r.data);
    });
};

I got a access_token and a refresh_token, but never got a response to set a laravel_token cookie (only laravel_session).

When I try to check the authentication, with the following code, I got a 401 Unauthenticated:

window.check = function() {
    window.axios.get('/api/user').then(r => {
        console.log(r.data);
    });
};

I'm using the bootstrap.js example I got with Laravel 5.4:

window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
let token = document.head.querySelector('meta[name="csrf-token"]');
if (token) {
    window.axios.defaults.headers.common['X-CSRF-TOKEN'] = token.content;
}

Axios is sending the following headers:

Debugging the CreateFreshApiToken middleware, on handle function, the return value is always false on function shouldReceiveFreshToken.

for some reason, the $this->guard is always null, so this function is always false:

protected function requestShouldReceiveFreshToken($request)
{
    return $request->isMethod('GET') && $request->user($this->guard);
}
tobecwb commented 7 years ago

I got. Apparently the CreateFreshApiToken only works if I authenticate the user using a more "traditional" way (Auth class, like Auth::attempt or Auth::login)

skyrpex commented 7 years ago

Hi there,

if you pay attention, you'll see that your request must be GET in order to the middleware to create an API token.

You can easily get the token by sending a dummy GET request to your site AFTER you're logged in.

tobecwb commented 7 years ago

I don't get...

Tried the following code:

window.axios.post('/oauth/token', data).then(r => {
  if (r.status === 200) {
    console.log(r.data);
    window.axios.get('/', data).then(r => {  // dummy get
      console.log(r.data);
    });
  } else {
    console.log(r.status);
  }
}).catch(r => {
  console.error(r.data);
});

still, no laravel_token cookie. even if I refresh the browser (do force a get) after call the function I don't get the laravel_token

skyrpex commented 7 years ago

Ah, I think I get what's wrong. Your request is logged in via the access token, so it means that you must use your api guard (instead of the null guard, which stands for web by default).

Web guard = logged in via laravel_session cookie. API guard = logged in via token. Try this:

// kernel.php
protected $middlewareGroups = [
    'web' => [
        // ...
        'create_fresh_api_token:api',
    ],
    // ...
];

protected $routeMiddleware = [
    'create_fresh_api_token' => \Laravel\Passport\Http\Middleware\CreateFreshApiToken::class,
];
tobecwb commented 7 years ago

still same error... 401

ps: The \Laravel\Passport\Http\Middleware\CreateFreshApiToken was already on kernel.php (see my first post, where I write "So, following up the Consuming Your API With JavaScript section of documentation, I added the \Laravel\Passport\Http\Middleware\CreateFreshApiToken::class in App\Http\Kernel.php").

Now, I just changed a little to make things like your post.

and no laravel_token cookie!

Response header on GET after POST on /oauth/token

Set-Cookie:XSRF-TOKEN=eyJpdiI6ImZaT2RjMUN5bGJRZk1BeFZvUDVvUWc9PSIsInZhbHVlIjoiWVk5NXZjRCtvc1k0NmZrUEdqMGNWNW1Nb1hNM0pjdHM5b25CY2xuWmg1T2hsNkxPRE5SRlVsZWRwN2ZjcHEwUktcLyszSmxnK3kzTmN2QmJtaTA5N1h3PT0iLCJtYWMiOiIyMmE5Y2JjYjdhZWEyNjQwMzc1MGVkNDc3ZDM4OWIxMWY5MTdhOTM1NmRkYmRlMThiNzFhZjYwNDFkMTlkNzFiIn0%3D; expires=Tue, 06-Jun-2017 18:08:25 GMT; Max-Age=7200; path=/
Set-Cookie:laravel_session=eyJpdiI6ImJ0VlF6Zmx6TldRU2o5SVFnYVRrUkE9PSIsInZhbHVlIjoiVzRPSkkyM1lYV1U4eEVVblpIdDNRVExST3NVdFVlSEhXSTVhWWJTM0FvYW5OZ0loT3R5Z29qU3pTT0xhZFFDWVVRMG90d05wZWtiZm5WcW5EUUZCbGc9PSIsIm1hYyI6IjA3ODMyZmFmMTUxNTYwNWUwODUyZmUxYzM4MzU3MDdkOTdlYmU5YjgxMzU5NjkzMWJmOWYwNDkwNzhjYWNmNWUifQ%3D%3D; expires=Tue, 06-Jun-2017 18:08:25 GMT; Max-Age=7200; path=/; HttpOnly
skyrpex commented 7 years ago

Are you sending the access token to the GET request, too?

tobecwb commented 7 years ago

I'm sending the X-CSRF-TOKEN and X-XSRF-TOKEN.

Here is all the log I can get from the browser:

GET /:
CSRF_TOKEN from Laravel = 2oVGXkDLTfduydnUHSiyFajdAOITRMOWe2pCcVMq

Response Headers:
  - Set-Cookie:XSRF-TOKEN=eyJpdiI6InZ2Y09vVTQrditCbjVyNUlGN01Pd3c9PSIsInZhbHVlIjoieml2UDlJclhuMVFWeGw1QkhOc0l5T2Y4d0lOS2E0R0RFajlEdXdpeW5MVzZoVzE3b3kyXC9waHNkcVg0dDhDRVBiUnRUTFlVYjRtTlZ2OWx4UUJLVVdBPT0iLCJtYWMiOiJmMTFkYjc5ZTdmY2MwYzEwMWNjZTU0ZmM4NGE5ZDI5ZDkwODAxNmVkMDI2MDY0ZGI4M2UxY2MwZDkwNGUzODRlIn0%3D; expires=Tue, 06-Jun-2017 23:56:39 GMT; Max-Age=7200; path=/
  - Set-Cookie:laravel_session=eyJpdiI6IlNWdU5aOVBFR2pVaDNrWXdwbU5xalE9PSIsInZhbHVlIjoiN2pkd0lFSldCbmFDOWROZlhCS1FndHRuSEJjcVA3SlBnTFFmcGY0ZU5sMUZZQ0poY1BFcXQ5UjNic1pPMHFBWm1aNkk2QURVdEw4WVZtOVllZkN4U3c9PSIsIm1hYyI6IjY2YmMyYjEzNjllYmE4MmViMGJiNmUwZjhjYzRkMjZkNTBjZGVmNjgyY2FkOGM2ZGQ3NmE1MzBlZDQ0MGFmMzgifQ%3D%3D; expires=Tue, 06-Jun-2017 23:56:39 GMT; Max-Age=7200; path=/; HttpOnly

-----

POST /oauth/token:
Request header: 
  - Cookie:XSRF-TOKEN=eyJpdiI6InZ2Y09vVTQrditCbjVyNUlGN01Pd3c9PSIsInZhbHVlIjoieml2UDlJclhuMVFWeGw1QkhOc0l5T2Y4d0lOS2E0R0RFajlEdXdpeW5MVzZoVzE3b3kyXC9waHNkcVg0dDhDRVBiUnRUTFlVYjRtTlZ2OWx4UUJLVVdBPT0iLCJtYWMiOiJmMTFkYjc5ZTdmY2MwYzEwMWNjZTU0ZmM4NGE5ZDI5ZDkwODAxNmVkMDI2MDY0ZGI4M2UxY2MwZDkwNGUzODRlIn0%3D; laravel_session=eyJpdiI6IlNWdU5aOVBFR2pVaDNrWXdwbU5xalE9PSIsInZhbHVlIjoiN2pkd0lFSldCbmFDOWROZlhCS1FndHRuSEJjcVA3SlBnTFFmcGY0ZU5sMUZZQ0poY1BFcXQ5UjNic1pPMHFBWm1aNkk2QURVdEw4WVZtOVllZkN4U3c9PSIsIm1hYyI6IjY2YmMyYjEzNjllYmE4MmViMGJiNmUwZjhjYzRkMjZkNTBjZGVmNjgyY2FkOGM2ZGQ3NmE1MzBlZDQ0MGFmMzgifQ%3D%3D
  - X-CSRF-TOKEN:2oVGXkDLTfduydnUHSiyFajdAOITRMOWe2pCcVMq
  - X-Requested-With:XMLHttpRequest
  - X-XSRF-TOKEN:eyJpdiI6InZ2Y09vVTQrditCbjVyNUlGN01Pd3c9PSIsInZhbHVlIjoieml2UDlJclhuMVFWeGw1QkhOc0l5T2Y4d0lOS2E0R0RFajlEdXdpeW5MVzZoVzE3b3kyXC9waHNkcVg0dDhDRVBiUnRUTFlVYjRtTlZ2OWx4UUJLVVdBPT0iLCJtYWMiOiJmMTFkYjc5ZTdmY2MwYzEwMWNjZTU0ZmM4NGE5ZDI5ZDkwODAxNmVkMDI2MDY0ZGI4M2UxY2MwZDkwNGUzODRlIn0=

Request payload:
  {"grant_type":"password","client_id":2,"client_secret":"fNoSz3LnZYnHE1MHgUV3FlJ3gWEpACd5gCvJEW2p","username":"abc@def.com","password":"admin","scope":""}

Response (no headers, only "Server", "Transfer-Encoding", "Content-Type", etc:
{"token_type":"Bearer","expires_in":1296000,"access_token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6IjBlODkxNWIzMzc2OWJmZDY5NDkzZTllYWY2ODI5Zjg2YjlmMGVkNzYyMjYxMWViMmU4Yzc4MTk5ZGM4MDBlZDNhYTBlNmRmMjZhYmIzYjdkIn0.eyJhdWQiOiIyIiwianRpIjoiMGU4OTE1YjMzNzY5YmZkNjk0OTNlOWVhZjY4MjlmODZiOWYwZWQ3NjIyNjExZWIyZThjNzgxOTlkYzgwMGVkM2FhMGU2ZGYyNmFiYjNiN2QiLCJpYXQiOjE0OTY3ODYyNTAsIm5iZiI6MTQ5Njc4NjI1MCwiZXhwIjoxNDk4MDgyMjUwLCJzdWIiOiIxIiwic2NvcGVzIjpbXX0.ckLOB5u3IQj9b2gkUGjLUnHlcEiTr9KKHt0RykeuqsH8c5yrgkFjI0tH_jm5t2XEDuqFPqYaHjOfC3LFxkGGzL5YQgt2Bhws8OKrfSwwXqXEhibNd-SriTLdjmDyA8578L9MzDx2fmc7FLs3z3kQGDH1AJ4r_qmLCQl4MvQ5k9ryG8YRPM2OWDCMnFPRe2NXf-jLK_-XI_8e4hDRilp5qnJlIbpxHyWLtrhwkJqvNA8gUmeLMzc0cVWwNjDdN-v9ie4GDnX4ipurujVKyIhI79eMrV-qd49erHDW-cXHlcT6AjxojSDCW-LwK9QsgBKdLTke251xCFrgNWaSdVlTtkvcljJ3w_m9wE_mO8KqP3NTG09Ka4IDuGNLbLvEOTeoDBhL0suv61-xYteRb_CBNAdGK_INPaAihfnu38d31eDAFoyrK1imeUpyIJzIK37-qKFXkbXdX3lYQYot3Nag614_OFz_bnWhqPdXmtCoAjskCPJePyUd-QtWlJF9DjKWNSMQuJSG_5VnHdRNAJERAAsYD5bfAAlKNIfIFrmvbapc3dtcYmc6m9ZqKJV3dSw1l9MvOWKJ-O2E9ybJ1ln34JWy7gxevHt7NUplw6mydZgtUsmcYMfEHSNVinPR5AsR1Duor6TGWBHq719nUwZWpR86sNtQ5B8UEi7_PYvUV-Y","refresh_token":"kHePCDzOlNzdcUj4J9hJLkaClAnZAL0NHN4UnTwwxc0EWVpmJfwGzAGizg\/ClxmFXF91UA2WwQuvS1la5yoo15I\/pq+LI8L0WPTLUh1yhyaSdtJ\/4OJGCAu++Kj\/nKGM\/A0S9qKuwqEx5YTOrRBj0FFDfME\/F9Ef9\/LRs+JYybDr6IhgijI2WxcW76BPDrnWwbm60uNamcmHnSmA3Hzmd7F10FOp5O0oMBnAs2DJhs2yRsFOpMhPKcHByzasChAL\/AdR8XWihdynguuIfkZj8CbAUoCobDVVzcTUd1zvPMxgCJdlituiNcev5xc21VQFYmbN6P30EUCLKFyKbnMA39\/oTbJ7EvebspIWFIiy7kNKOATuDNc4F9fP9BhrO+uml7AinJ+h4jUucAqd\/+cqCWNyvv82DwyBWWM2loxP9KZW7cQOvJYoSTfU+XWrMI26QcCb7PZRrUunm5\/4WeVdrRGo4kpNmDoqkToVcz3g40Ek\/xwwAZsmKgZCClaANglg0gM5ZSiqP2XpfaBUcHq7FOpsnw1Q4r\/bL2EPJlYU+4dEDaTT95Z2KWh+XC5I9qIjj+vCCbc8W3DJxdYyyRmKqiTjj48tKTG5NHqtoeNWkDCFohJ1WR9WV0peqfBadDqXLwrQprCCqVyi5Lt6xWcPlnA4s6WTiV4Js1G+t6TRu\/M="}

----

GET /api/user:
Request header:
  - Cookie:XSRF-TOKEN=eyJpdiI6IkpCdzJnYm05NVpHUU52Ym9rRnNUTVE9PSIsInZhbHVlIjoiam9FeVVoMHVvaHNBc0t5UzZMTXRCeGNMcEVKd0lOUUxESkR6OWQ0YyszRkp1RUlFVkdLRHRqOGNkTWpxVXFBMnA1V1V1c1wvTnZjcHZxaTlzQWE4VTV3PT0iLCJtYWMiOiJkZTRiNzkwYzIzNWJiZWU1ODhkYzRkMmE2MjcxNjIwZGFiZWNmNzYwOTg0MzllNGVhNmRlNTA2MzM4MjFmNzMxIn0%3D; laravel_session=eyJpdiI6IldXSFlKY1Rocm9kTVIzOEdlVEVROEE9PSIsInZhbHVlIjoidVhWY1RZdjlUQ0RFWFF1aGJOMVJwUFlSa0pTbTV3ZXJ4MUNMVXBhWUFYMkNvd21pOUVTZUFJV0lZeTJJYTJXNktUTnlLbGZuRk9uUjMxSjFBVjNxWHc9PSIsIm1hYyI6ImJiY2E1OGYzOTU3OWZjZTYxYjE4M2RhNWJhZmZmYzE5YjQwODNiNmJkOTU2OTc0NzgxMjA1YmU2NzM1MGVkNjQifQ%3D%3D
  - X-CSRF-TOKEN:2oVGXkDLTfduydnUHSiyFajdAOITRMOWe2pCcVMq
  - X-Requested-With:XMLHttpRequest
  - X-XSRF-TOKEN:eyJpdiI6IkpCdzJnYm05NVpHUU52Ym9rRnNUTVE9PSIsInZhbHVlIjoiam9FeVVoMHVvaHNBc0t5UzZMTXRCeGNMcEVKd0lOUUxESkR6OWQ0YyszRkp1RUlFVkdLRHRqOGNkTWpxVXFBMnA1V1V1c1wvTnZjcHZxaTlzQWE4VTV3PT0iLCJtYWMiOiJkZTRiNzkwYzIzNWJiZWU1ODhkYzRkMmE2MjcxNjIwZGFiZWNmNzYwOTg0MzllNGVhNmRlNTA2MzM4MjFmNzMxIn0=

Response (no headers, only "Server", "Transfer-Encoding", "Content-Type", etc:
Status Code: 401 Unauthorized
  - {"error":"Unauthenticated."}

my bootstrap.js, called in every refresh on the browser (in my main script file, the first line is require('./bootstrap');) (this is the same piece of code from bootstrap.js that came with the default installation of Laravel:

window.axios = require('axios');
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';

let token = document.head.querySelector('meta[name="csrf-token"]');

if (token) {
    window.axios.defaults.headers.common['X-CSRF-TOKEN'] = token.content;
} else {
    console.error('CSRF token not found: https://laravel.com/docs/csrf#csrf-x-csrf-token');
}

Edit: looking at the headers, I see that the access_token is never used in header. I really don't known if this is the error. I known if I send a header with 'Authorization' => 'Bearer '.$accessToken, everything works fine, but what's i'm trying to do is:

Typically, if you want to consume your API from your JavaScript application, you would need to manually send an access token to the application and pass it with each request to your application. However, Passport includes a middleware that can handle this for you. All you need to do is add the CreateFreshApiToken middleware to your web middleware group

1efty commented 7 years ago

Is your definition of \Laravel\Passport\Http\Middleware\CreateFreshApiToken the last item in the array of the web midddlewareGroups?

dschreij commented 6 years ago

I am struggling with the same (or similar) issue: see my stackoverflow post and my post on Laracasts.

Locally everything works fine and I can log in. The trouble starts when logging out, and then trying to log in again. After a logout the laravel_token cookie is deleted, but the login right after does not recreate it. If I do a hard refresh the cookie is set directly (without having to enter credentials again). So it appears as if the cookie is not being set with an asynchronous post request to the /login function (or by a dummy GET request right after).

On a remote server, things are even weirder. When the page is loaded for the first time, I can try to login endlessly and always get a "401 unauthenticated" response. Only after a hard page refresh, the token is set and the user is authenticated. My login (or authenticated) function in the LoginController is as follows. Do I need to explicitly call a function that sets the laravel_token?

class LoginController extends Controller
{
    use AuthenticatesUsers;

    /**
     * Where to redirect users after login.
     *
     * @var string
     */
    protected $redirectTo = '/';

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        // $this->middleware('guest')->except('logout');
    }

    /**
     * Get the needed authorization credentials from the request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    protected function credentials(\Illuminate\Http\Request $request)
    {
        //return $request->only($this->username(), 'password');
        return ['email' => $request->{$this->username()}, 'password' => $request->password, 'active' => 1];
    }

    /**
     * The user has been authenticated.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  mixed  $user
     * @return mixed
     */
    protected function authenticated(\Illuminate\Http\Request $request, $user)
    {
        $user->last_login = \Carbon\Carbon::now();
        $user->timestamps = false;
        $user->save();
        $user->timestamps = true;

        return (new UserResource($user))->additional(
            ['permissions' => $user->getUIPermissions()]
        )->response();
    }

    /**
     * Log the user out of the application.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function logout(\Illuminate\Http\Request $request)
    {
        $this->guard()->logout();
        $request->session()->invalidate();
    }
}
BobbyBorisov commented 6 years ago

@dschreij @tobecwb guys! i am struggle too with problem. Did you fix yours? Basically what i want is to have web app and parts of it to be with VueJS. For axios calls i want to use cookie token but so far is it not working. even with scopes.

dschreij commented 6 years ago

@BobbyBorisov I've managed to make it work, but I'm still not happy about it; it's not optimal. When the session expires, users get bounced back to the login screen (which is good), but then have to log in about three times before the system accepts it. There must be something wrong with the laravel_token validation on the server-side, since the cookie is transmitted correctly. Sadly, I haven't been able to dive into this (the project has other priorities a.t.m.).

I opened another issue about this (because I thought my case was a bit different) in which I provide more details about my current approach: https://github.com/laravel/passport/issues/564

lady-ady commented 5 years ago

@BobbyBorisov by any chance did you find a way to solve this issue?

driesvints commented 5 years ago

@tobecwb @lady-ady @BobbyBorisov can you all three show me what you are returning from your controller method? Please realize that the laravel_token cookie will only be set if:

Please share the code from your controller which you're returning from the get request you're performing. I have a slight hunch at what's going on but need more info.

carsonlius commented 5 years ago

i have the same problem ! laravel5.5 php7.2
anybody fixed the issue ? @tobecwb @driesvints

driesvints commented 5 years ago

@carsonlius please provide the info I've requested from above.

Denoder commented 5 years ago

One scenario I encounter this is is, if i set my default guard to api it wont create the token, but if i set it to web it will do so after i've been authorized after login.

skyrpex commented 5 years ago

One scenario I encounter this is is, if i set my default guard to api it wont create the token, but if i set it to web it will do so after i've been authorized after login.

The login is supposed to happen using the web guard (using a traditional form with a CSRF token, for example). You can pass the guard as a middleware option too.

There's another problem: the login call uses a POST request. What I usually do is to extend the Laravel\Passport\Http\Middleware\CreateFreshApiToken middleware to inject a fresh token for POST requests too. For that, you should change this:

    protected function requestShouldReceiveFreshToken($request)
    {
        return $request->isMethod('GET') && $request->user($this->guard);
    }

to this:

    protected function requestShouldReceiveFreshToken($request)
    {
        return $request->user($this->guard);
    }
MovingGifts commented 5 years ago

@carsonlius @skyrpex Here is the code I am using, but I only get back Unauthenticated when accessing the dashboard under the auth:api middleware. Since the login/register page is all part of the js SPA, (not the default laravel html server auth pages), I added the Route::post('login', 'AuthController@login')->middleware('web'); thinking this will act the same way. I am able to create an access token in AuthController@login method and it gets saved to the db, I can even return that token to the front end, so the login part seems working based on the response, but when I fetch the dashboard data, I get the following error instead:

development.CRITICAL: Illuminate\Auth\AuthenticationException: Unauthenticated. in /vendor/laravel/framework/src/Illuminate/Auth/Middleware/Authenticate.php:66

// Kernel.php
protected $middlewareGroups = [
    'web' => [
        \App\Http\Middleware\EncryptCookies::class,
        \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
        \Illuminate\Session\Middleware\StartSession::class,
        // \Illuminate\Session\Middleware\AuthenticateSession::class,
        \Illuminate\View\Middleware\ShareErrorsFromSession::class,
        \App\Http\Middleware\VerifyCsrfToken::class,
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
        \Laravel\Passport\Http\Middleware\CreateFreshApiToken::class, // added for passport 
    ],

    'api' => [
        'throttle:60,1',
        'bindings',
    ],
];

// routes/api.php (you can ignore the XYZChecker middleware, it just checks for xyz headers)
Route::group(['namespace' => 'api', 'middleware' => 'XYZChecker'], function() {
  // Public Routes Group
  Route::group([], function() {
      // Note sure if using web middleware here is correct...
      // Route::post('login', 'AuthController@login'); // original
      Route::post('login', 'AuthController@login')->middleware('web'); // testing web middleware

      Route::get('users/all', 'UserController@getUsersAll');
  });

  // Auth Only Routes Group
  Route::group(['middleware' => 'auth:api'], function() {
      Route::get('dashboard', 'UserController@getDashboard'); // returns 'Unauthenticated'
      Route::get('logout', 'AuthController@logout');
  });
});

// UserController.php
...
public function getDashboard(){ // testing with api sessions instead of auth bearer token
    return response()->json(['success' => true]);
}

To get the dashboard I am also passing the correct X-CSRF-TOKEN and X-Requested-With headers. Any idea why the Unauthenticated still shows up when attempting to get the dashboard data?

skyrpex commented 5 years ago

Maybe the LoginController is still using your default guard, which you changed to api? You should explicitely change it to webin that case.

MovingGifts commented 5 years ago

@skyrpex Thanks for the feedback, did you mean the default guard in config/auth, if so it is set to web already:

// config/auth.php
'defaults' => [
    'guard' => 'web', // set to 'web' by default
    'passwords' => 'users',
],

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

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

If you're talking about the default LoginController.php it looks like this:

// LoginController.php
namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;

class LoginController extends Controller {
    /*
    |--------------------------------------------------------------------------
    | Login Controller
    |--------------------------------------------------------------------------
    |
    | This controller handles authenticating users for the application and
    | redirecting them to your home screen. The controller uses a trait
    | to conveniently provide its functionality to your applications.
    |
    */

    use AuthenticatesUsers;

    /**
     * Where to redirect users after login.
     *
     * @var string
     */
    protected $redirectTo = '/home';

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
         // Tried both below, but still get 'Unauthenticated'
        $this->middleware('web')->except('logout'); // testing
        // $this->middleware('guest')->except('logout'); // orig
    }
}

But I am explicitly using a custom login controller called AuthController.php as seen in the routes file login method, so I tried adding the guard explicitly there if that is what you meant, but still get Unauthenticated:

// AuthController.php
namespace App\Http\Controllers\API;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Auth;
use App\User;

class AuthController extends Controller {
    public function __construct() { // testing...
        $this->middleware('web');
    }

    // using with passport.....
    public function login(Request $request) {
        // grab credentials from the request
        $credentials = $request->only('email', 'password');
        $user = User::where('email', $request->email)->first();

        if($user) {
          if(Auth::attempt($credentials)) {
            $token = $user->createToken('Laravel Password Grant Client')->accessToken; // gets added to db
            return response()->array(compact('token')); // returns token to front end, meaning this works
          } else {
            return response()->error('Login Failed');
          }
        } else {
          return response()->error('Login Failed');
        }
    }
}

This is kind of confusing... Maybe the issue is that I am not using the out of the box web laravel html web login pages, but I think we're applying the web middleware on the login route in the api, also added it to the constructor explicitly too just to be sure, but it seems to still not authenticate. Does the routes/api.php look correct in terms of middleware usage for it to authenticate?

skyrpex commented 5 years ago

@MovingGifts Check if the login request creates the laravel_token cookie. The default CreateFreshApiToken middleware won't add the cookie if the request is different than GET.

MovingGifts commented 5 years ago

@skyrpex I used your advice above on changing Laravel\Passport\Http\Middleware\CreateFreshApiToken middleware to inject a fresh token for POST requests too.

From this:

protected function requestShouldReceiveFreshToken($request) {
    return $request->isMethod('GET') && $request->user($this->guard);
}

to this:

protected function requestShouldReceiveFreshToken($request) {
    return $request->user($this->guard);
}

When logging in the front end, with POST a Response Header set-cookie: laravel_token=eyJ... is returned in the response headers. The next GET goes to fetch the dashboard with the Request Header cookie: laravel_token=eyJ..., X-CSRF-TOKEN and X-Requested-With all set, but still get Unauthenticated. I am stumped and not sure what is missing as it seems everything should be working by now, based on the the cookie laravel token being issued and sent back with the necessary headers.

MovingGifts commented 5 years ago

I don't know if this could be a VerifyCsrfToken token issue:

This is what it looks like:

// VerifyCsrfToken.php
namespace App\Http\Middleware;

use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;

class VerifyCsrfToken extends Middleware {
    /**
     * The URIs that should be excluded from CSRF verification.
     *
     * @var array
     */
    protected $except = [
        'api/*', // custom added for barryvdh laravel-cors
    ];
}

This is what I used before when using the manual Authorization Bearer JWT token approach, instead of the passport session with the jwt encrypted in it. When I comment out the whole api/* line though, since I assume that now laravel needs the CSRF token, since we use it in the header for passport session to work, I get:

Illuminate\Session\TokenMismatchException in /vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php:70 but since the token is being sent, I can't understand why it does not work (I cleared session and cookies too, but same issue). Is it correct to disable the laravel-cors in this case?

MovingGifts commented 5 years ago

Do you guys think it's because the login is part of the SPA as opposed to the default laravel login page, or should it be able to work since we post the required fields anyway from the SPA login page? I am really trying to keep the whole login/register experience on the SPA side 100%, and only post the values to the api controller. This is much better UX than having /login and /register on the laravel web.php server side decoupled from the SPA client side routing, no awkward refreshes and lightning fast.

I read many threads about SPA and laravel passport authentication, which say they should be decoupled in order to store the encrypted jwt in session cookie (but the threads are older, and maybe there is a new way with the CreateFreshApiToken), but I am trying to do it from just the SPA and jwt in session cookie as I mentioned. We don't want to save the jwt to local storage for obvious security purposes, even though the Bearer Authorization header works 00% on client, but is not secure.

So can it be 100% client side SPA login with CreateFreshApiToken handling the jwt token security via sessions?

I've been stuck on this for two full days and it's critical to know how to proceed, since we launch soon.

Please let me know if anyone has any idea.

skyrpex commented 5 years ago

your current problem may be that the CSRF token you're sending to the API endpoints is encrypted. I had to add another cookie with the CSRF token without encryption to use it with axios

MovingGifts commented 5 years ago

I couldn't get it to work, had to use a custom implementation. Thanks for helping out though @skyrpex!

driesvints commented 5 years ago

I looked into this further and I can't reproduce this. Works on a fresh Laravel install. Closing this as the original authors haven't replied anymore.

juslintek commented 4 years ago

Had the same problem, just copy middleware from the web to API. :-) Instructions are vague, they probably think that you're going to have API endpoints setup in the web group. :-)

So basically do this in Kernel.php:

...
        'api' => [
            // disabled temp
            // 'throttle:60,1',
            ...
            \Illuminate\Session\Middleware\StartSession::class,
           ...
            \Laravel\Passport\Http\Middleware\CreateFreshApiToken::class,
        ],
...

But guess the problem is still the same. As long as you're using API routes group for login.

    /**
     * @param Request $request
     * @return JsonResponse|Response|mixed|StreamInterface
     */
    public function login(Request $request)
    {
        try {
            $authResponse = $this->http->post(
                config('services.passport.login_url'),
                [
                    'form_params' => [
                        'grant_type' => 'password',
                        'client_id' => config('services.passport.client_id'),
                        'client_secret' => config('services.passport.client_secret'),
                        'username' => $request->username ?? $request->email,
                        'password' => $request->password,
                    ],
                ]
            );

            return $authResponse->getBody();
        } catch (BadResponseException $exception) {
            return $this->handleException($exception);
        }
    }

I've set as well to set a token cookie on any request. But seems like it is not even verified in any way and not be used to retrieve user data.

juslintek commented 4 years ago

laravel_token sucks removed session functionality from API laravel at all and it's sorted :D

tonilam commented 3 years ago

I inspect all the way the authentication goes. in Authenticate.php autenticate(array $guards), when doing $this->auth->guard($guard)->check(), it return false, then I go to have a look on the TokenGuard.php decodeJwtTokenCookie($request), it will decrypt the passport cookie -> which the laravel_token, and it gives out DomainException: Malformed UTF-8 characters. I think it is the root cause of why the authentication failed. After all, I check the laravel_token in jwt.io, it says "Looks like your JWT header is not encoded correctly using base64url"