Skip to content
This repository has been archived by the owner on Sep 9, 2020. It is now read-only.

Switching authentication to passwordless #8

Open
iceberg53 opened this issue Oct 4, 2018 · 3 comments
Open

Switching authentication to passwordless #8

iceberg53 opened this issue Oct 4, 2018 · 3 comments

Comments

@iceberg53
Copy link

Environment

Laravel Homestead

Issue description

I tried to replace the socialite authentication with a passwordless medium style authentication.
First, the user provides his email where I send an authentication token (I replaced the social authentication links in the login modal with a form input for email );
Then, after clicking on the authentication link, the user is either created or retrieved by email and redirected to the home page.
In the authentication controller previously used for socialite, I check the token validity (existence and expiration ), and I log the user in with Auth::login($user) , $user being the user for the mail address the token came from.
I used Auth::login($user) by following the example for socialite authentication in roastandbrew code.

I also faced a bug related to the session storage file as described at laravel/framework#22514 that I bypassed by changing the session driver environment variable to cookie.

After redirecting the user, I noticed that despite the Auth::login($user) action I did, the user didn't appear as authenticated (userLoadStatus==3).

When I checked the API\UsersController.php, I saw that we get the authenticated user with Auth::guard('api')->user(), and I didn't understand why.

I thought that there would be something like Auth::guard('api')->login($user), but it throws an exception for non-existent method.

When I replace Auth::guard('api')->user() with response()->json(Auth::guard('api')->user()), the user appears to be logged in the application whereas a curl call with the following command :
curl -X GET -H 'Accept: application/json' http://memoire.local/api/v1/user returns an empty response.

dd(Auth::guard('api')->user()) throws an error when called from my token model dd(Auth::guard()->user()) does return a user.

I got confused, and after searching from yesterday, I decided to open this issue to know if I missed something.

By the way, your code is very fine and easy to understand, Thank you for it and the tutorial.

Steps to reproduce the issue

I mainly followed the steps in this medium article https://medium.com/@hive_adrian/password-less-email-authentication-in-laravel-ecf5388efe74 by adapting it to the single page app context (no blade view, response->json instead of views as return values, etc, ...)

Additional details / screenshots

I want to point out that my authentication routes were initially in the web.php but there was a 419 error which led me to write them inside the routes/api.php file.

Thank you very much !!

@danpastori
Copy link
Contributor

Hi @iceberg53 ,

Thank you very much for the incredibly detailed issue this helps tremendously!

So there's a couple tricks going on here. You are using the new password-less authentication and we are using Laravel Passport. To be honest, I haven't messed around with the password-less authentication yet, but I think I got the core of the concept by reading the article you provided. With Laravel Passport, when you authenticate a user, new API tokens get created. The first thing you will need to do is make sure that your User.php model has the following traits:

<?php
/*
  Define the namespace for the user model.
*/
namespace App\Models;

/*
  Defines the facades used by the model.
*/
use Laravel\Passport\HasApiTokens;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;

/**
 * Defines the user model.
 */
class User extends Authenticatable
{
    use HasApiTokens, Notifiable;

That being the HasApiTokens. This means when your model is authenticated the tokens are created. The awesome thing about Laravel passport and on the web is it stores a cookie. With each request, that cookie gets sent that has the information needed to apply the tokens to the request on behalf of the user. So the guard Auth::guard('api')->user() gets the user based off of the single authentication from the request and uses that user to handle the request. The auth:api middleware that blocks routes checks these tokens before continuing the request.

Essentially, I would check to make sure you have your model with the HasApiTokens and you have the middleware set up correctly. Otherwise your user WILL be authenticated, but the tokens haven't been made and won't be accessible from the API.

Let me know if that helps! If you have any code examples, that'd be helpful.

@iceberg53
Copy link
Author

I already included the HasApiTokens in the user model.

You can find my relevant source files here

I didn't include my config/auth.php because I didn't change anything in the config.

Excuse me for the late reply and once again thank you for the work you did.

@danpastori
Copy link
Contributor

@iceberg53 So I finally got a chance to check out some of the code, sorry bout that! What I think the issue is in Token.php on line 48 you create a new token. I believe this overwrites what Passport creates.

Also, the guard only returns the authorized user if the call to load the user from the guard is after a route that is protected by the 'auth:api' middleware. So the request comes in -> hits the middleware -> middleware checks the tokens -> creates a token -> then the user is authenticated and you can load the user like normal. If it the user load call is not behind this middleware, then you have to check the guard which goes through the process of checking the tokens.

This goes hand in hand with your Token.php file. I'd first remove the new token creation (if focusing on having Passport handle the tokens). At the end of the file when you use the default guard and get a user, that's correct. This is because the request didn't pass through the auth:api middleware.

If you are loading a user form a route that does not pass through the auth:api middleware use:
Auth::guard('api')->user()

If you are loading a user from a route that DOES pass through the auth:api middleware use:
Auth::user()

Hopefully that helps!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants