Laravel 8 API email verification flow using Sanctum - laravel

I'm currently making an API for a mobile app but I think I'm a bit confused with how email verification and authentication is meant to work. I'm attempting to implement the following flow:
User registers in the mobile app and it sends a request to the API
Laravel creates the user and fires off an email
User receives the email and clicks on the link
Laravel verifies the user and redirects them to the mobile app via deep-link
However when the user clicks the email link a "route login not defined" error is rendered.
Which makes sense, because the user is not authenticated at the time. But am I getting this wrong?
Should I authenticate the user prior to sending the email? And will that work, given that we're using Sanctum rather than "regular" authentication?
Currently this is what I'm doing:
// web.php
Route::get('/email/verify/{id}/{hash}', [EmailVerificationController::class, 'verify'])
->middleware('signed') //note that I don't use the auth or auth:sanctum middlewares
->name('verification.verify');
// EmailVerificationController.php
public function verify(Request $request)
{
$user = User::findOrFail($request->id);
if ($user->email_verified_at) {
return '';
}
if ($user->markEmailAsVerified()) {
event(new Verified($user));
}
return redirect()->away('app://open'); // The deep link
}
Is there any security risk here? Should I at any point authenticate the user before or after they click the link?
I wanted to avoid rendering "web views" as much as possible.

I think that the best way is to implement two different paths based on the source of the user.
Regular email validation for users coming from a browser
The user will just follow the link delivered by email, you can do that with or without authentication (maybe with transparent cookie authentication). If the validation is fulfilled redirect them back to the home page.
Mobile users coming from the mobile application
I would send a PIN (with some kind of expire mechanism) via email and ask them to put it inside the APP to verify the account. This can even be protected with auth middleware using the JWT token with the verification API call.
I don't see any security issue with this last one.

Related

Issue understanding Laravel 6.0 Passport with Password Grant Tokens flow

I'm having issues understanding the whole process of authenticating a client to consume my API built on Laravel. Some things just don't click for me right now.
I'm trying to implement an API and an OAuth server both on Laravel. The API will be consumed by a native mobile app that is trusted. The flow that makes more sense to me is "Password grand token" as described in the Laravel's Passport docs: https://laravel.com/docs/7.x/passport#password-grant-tokens
As i understand the implementation:
User installs my mobile app.
Upon installation, he's prompted with the "enter username/password" to continue to use the app
Upon hitting submit, i make a POST request to my Laravel oAuth server implementation on "/oauth/token" with "grant_type", "client_id", "username", "password", "scope". I'm leaving out the "client_secret" because i understand that it's not a good idea to store the secret on the client device.
The server then checks the already created( `php artisan passport:client --password` ) "client_id", "username", "password" and "response_type"
If all matches, it generates a token, and responds with "acces_token" & "refresh_token"
I can make now make calls to my API's endpoints "/api/whatever/method_name"
My issue is at point 4. I can only issue the access token if the user already exists in my database, but i'm assuming it's the first time the user uses my app. postman_response
Do i also need an "authentification" step, in witch the user sends username/password and the OAuth server prompts the "authorize app" to use your data, and at this point to save the user in the database and only then proceed?
Usually you have an register route, that is without authorization else you have no entry into the application. Imagine your routes file.
Route::middleware('auth:api')->group(function () {
Route::post('/todos', 'TodoController#index');
});
// Without auth
Route::post('/register', 'RegisterController#register');
For hiding credentials, it is often easier to do a proxy approach, so you backend can hold client_id and client_secret, since it will always be the same (unless you are building an oauth server).
Route::post('/login', 'LoginController#login');
Which will receive username and password, internally call oauth/token and add client_id and client_secret in the process and return the token. To save some calls through the signup, you can do the same approach after you have registered, get the oauth token, with the credentials you have at registrering and return the token imediatly.
I would recommend the following:
In log in method, check if user exists.
If exists, do log him in.
else, first register him up, and then log him in
lastly, return access token

Unable to get authenticated user using Laravel 5.8 and Auth0

I have a Laravel 5.8 API that I want to secure using Auth0. So far I've followed every step of this tutorial:
On the front side, Login/logout links are currently implemented in Blade, and this works fine, though the rendered content on the page is done using Vue Router, making AJAX requests to the API for the data.
The default User model in Laravel has been modified to store name, sub, and email per the tutorial, and this populates as well.
The API endpoint is secured using the jwt middleware created during the tutorial, and I can successfully submit a GET along with a hard-coded Bearer auth token in Postman and get a good response.
However, at some point I'd like to be able to pass an access token off to Vue so it can do its thing, but I'm unable to get the current authenticated user. After hitting Auth0, it redirects back to my callback route with auth gobbledlygook in the URL. The route in turn loads a controller method, and everything even looks good there:
// Get the user related to the profile
$auth0User = $this->userRepository->getUserByUserInfo($profile); // returns good user
if ($auth0User) {
// If we have a user, we are going to log them in, but if
// there is an onLogin defined we need to allow the Laravel developer
// to implement the user as they want an also let them store it.
if ($service->hasOnLogin()) { // returns false
$user = $service->callOnLogin($auth0User);
} else {
// If not, the user will be fine
$user = $auth0User;
}
\Auth::login($user, $service->rememberUser()); // "normal" Laravel login flow?
}
I'm not an expert on the framework, but the last line above seems to start the "normal" Laravel user login flow. Given that, shouldn't I see something other than null when I do auth()->user(), or even app('auth0')->getUser()?
Try using a simple tutorial if you're a beginner, I would recommend this
It uses a simple JWT package to create a jwt token which you can get when the user authenticates.
JWTAuth::attempt(['email'=>$email,'password'=>$password]);

Check if user is authenticated in laravel API route

So I know API routes are not supposed to rely on sessions authentication, but my idea was to create some API routes, that could be used if needs be by third-party with proper authentication, but that could also be used internally, ie called to get the data I need for my web pages.
I changed the LoginController so that every time a user logs in, a Personal Access token is generated and stored in the database. When logging out, this token is deleted.
So as not to expose the token to the client side, I would like to use a middleware, that would detect, on an API call, if the request comes from a user who is already authenticated. If that's the case, I would retrieve the Personal Access token that belongs to the user, attach it to the request, and pass it onto the API.
Browser -- Query site.com/api/myRoute --> Middleware adds user's token to request if Auth::check() -- Pass-on request --> Controller
I've created a dummy API route to see if I can detect whether a user is authenticated, but that doesn't seem to work... I guess because the 'auth' middleware is not included.. however, if I do include it, I get redirected to home on every request...
Route::get('/test', function() {
if(Auth::check()) {
dd('Hello logged-in');
} else {
dd('Hello not logged-in');
}
});
Any lead on how to achieve that much appreciated!

Laravel 5.3 Ajax Login Customize Credentials

I am able to login via Ajax in Laravel 5.3
This is easily accomplished by making a post request to the login route with the proper parameters.
However, for my application, I am designing two ways for a user to be logged in - via the traditional email/password combination that Laravel already supports, and via an access code that would be distributed and allow the possessor of said code to login without an email/password combination. There is no "registration" in my app, there is just different levels of authentication.
Anyway, in /vendor/laravel/framework/src/Illuminate/Foundation/Auth I am editing the AuthenticatesUsers.php and understand that this function specifically handles the login attempts:
protected function attemptLogin(Request $request)
{
return $this->guard()->attempt(
$this->credentials($request), $request->has('remember')
);
}
My question is, how can I change the success of attempt() based on the content of the request?
In other words, if someone is sending an ajax access code it shouldn't be tested against an email/password combination, as it would obviously fail. Similarly, if they are sending an ajax with email/password parameters, it shouldn't be tested against the list of available access codes.
Am I on the right track? Where in Laravel can I go to make the Auth::attempt() contingent on request parameters?
I will not advice to edit a framework file.
You should rather write a middleware to handle identification of the type of authentication user is requesting for, before sending it to the controller. In your middleware,
public function handle($request, Closure $next)
{
// check if the request has access_code
$request->attributes->add(['using_access_code' => $request->has('access_code')]);
return $next($request);
}
And in your controller, you can check for positive test on this request parameter that we newly added (you can of course do this inside controller directly, but I personally like middleware to handle this because there are chances that you may want to add more functionality)
In your controller, if using_access_code is false, proceed with attempt() login, else, retrieve the user using access_code and manually authenticate the user using Auth::login($user). Let me know if you are unclear.

Laravel Social OAuth Authentication - Password?

I am using Laravel Socialite to register a user via an outside website. That works just fine, but I'm confused the best way to make sure the user is authenticated each time they come to my website.
Normally, a user will register with a username/email address and password. Then, we check the database against their inputted credentials and log that user in. But authenticating with an outside website, I don't have access to that user's password, just other credentials that are available (i.e. email address obtained from the 3rd party website).
So, if they register/login through an outside website, once the user is redirected back to my website, should I just authenticate like this? This is where I get confused because normally I include a 2nd key/value pair which is the password for the user.
if (Auth::attempt(['email' => $user['email']))
{
return redirect()->route('route');
}
UPDATE:
Shouldn't this simple Laravel authentication be sufficient? The 3rd party website I'm using to login handles the authentication workload. It seems that I'm just needing to authenticate through Laravel to be able to utilize the Auth facade for the current user.
The way I solved this was simple. The 3rd party website handles their authentication and once the user is redirected back to my website, they're good to go. So, I just push a session cookie to them and they're all set.
Auth::login($user, true);

Resources