How to implement ForgotPasswordController in SPA application with Laravel/Sanctum? - laravel

I'm using Laravel 7.x and sanctum. Logins are working and I would like to create a Forgot Password option from my SPA application.
I'm struggling with the basics as most of the examples in the documentation rely on the auth scaffolding. So far I've managed to get the following:
I have a controller class called ForgotPasswordController with a method called reset that receives the email to be reset via POST.
I've created a object: $user = User::where('email', $email)->get()->first();
At this point I'm too unfamiliar with the architecture to know where to go next, whether it's the Password facade, I see some additional classes in the Illuminat\Auth\Password namespace. My goal is to create an expiring token, email it to the user via the default email config (I know how to send the email / design the template) and then be able to make the webservice call that will allow the password to be resolved.
Here's what I think I know...
I've set CanResetPassword trait on my user models, which I believe are necessary to support the native methods for password reset
I believe the goal is to create a reset token keyed against the user email that expires after a period of time, then send that token appended to a url in an email (I don't know the architectural implications surrounding the generation of the token beyond the table row)
There's a Password facade with a sendResetLink method - but this
method can't work for spa applications because the base url of the
client app will be different, so I'm assuming something native will have to be re-written. In fact, calling this method will return an error of Route [password.reset] not defined.
I'm assuming I will need the password Facade, if so, what is the method to generate the token? Should I just email the link with the token appended or are there other architectural considerations to support the token expiration?
Apologies if my questions are flawed, I'm unclear on the architecture so I'm making assumptions.

Have you tried Laravel authentication? All authentication requirements have been moved to a package called laravel/ui.
By installing that package you can use Laravel authentication. It will take care of your registration, login, and forgot password processes.
This package will create some controllers for all those processes and those you need for forgot password are
ForgotPasswordController: will generate and send reset password links.
ResetPasswordController: will reset the password by getting user's email, new password, and reset password token.
But if you don't want to use the official Laravel package you should take these steps:
Show a "Request reset password form" to the user.
Validate the provided email by the user.
Generate a random reset password token and store it at DB (Need a table with at least two fields: email and token).
Send that token to the user(It's better if you send it as a URL parameter in the reset password link).
When the user navigated to the reset password page, ask for email again and validate the token by checking your DB table and matching the email and token.
Reset the password to whatever the user wants at this point.
Update: I use this piece of code for generating random tokens:
$email = 'user#email.com';
$token = \Illuminate\Support\Str::random(10);
while(\DB::table('reset_password_tokens')->where('token', $token)->exists()) {
$token = \Illuminate\Support\Str::random(10);
}
\DB::table('reset_password_tokens')->insert(compact('email', 'token'));

Related

Laravel Third Party API User Verification

I am trying to use a API which has a postable address where you can verify if a customer's username/password is correct, if so it returns a user ID.
What would be the best way of handling this, I need to query that postable API from the login form on my Laravel website to see whether or not a username / password is validated.
How can I use Laravel's Middleware to store a USER ID and session securely?
How can I create a Laravel session to allow someone to login to my Laravel site using their WHMCS client login?
The API I am using is https://developers.whmcs.com/api-reference/validatelogin/

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

Laravel Airlock Token

Introduction/Background
I'm looking to enable token authentication for multiple microservices and users. Both applications and users are $user objects.
I need to be able to authenticate once (hence token) using an auth server on a subdomain. I then need to be able to pass around a token that can be managed (revoked/refreshed whatever) by the Auth server.
The microservices are Laravel based, so using Airlock makes sense. Airlock generates tokens easily using:
$token = $user->createToken(now())
However, I see no method to manually check the validity of these tokens... So I assumed they are available in the database.
Airlock suggests that the token be returned as follows:
$token->plainTextToken
This produces a token, as expected. To my understanding, this is a public facing token. It does not match the token in the personal_access_tokens table.
Lets call these PublicToken and PrivateToken.
The private token is actually located in:
$token->accessToken->token
I want to be able to manually switch between a PublicToken. I assume Airlock is doing some security here.. and I want to invoke these secure methods required to check a PublicToken against the PrivateToken.
Please do not say "it's in middleware" ... The point is that I have multiple microservices and usertypes sharing a database. I have an auth server that will end up on secure architecture, and some of the other microservices wont be.... fundamentally I need to do a manual authentication because normal plug and play wont work. Using Airlock as the foundation is great. But I need to be able to know how to convert between public and private tokens.
Essentially I'm looking for the real version of the following psuedocode:
if( someTranslationFunction($public_token) == $private_token ) ...
TLDR: The problem
How do I validate a $token->plainText value against a $token manually?

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);

How to implement one controller mapping method for different scenarios

I have a spring controller method which could be called in different scenarios. here is the example...
#RequestMapping("/resetpassword")
public ModelAndView resetpassword( #Valid #ModelAttribute("resetpasswordForm") ResetPawdFormForm resetPawdFormForm, ModelAndView modelAndView){
... this method could be executed in 3 different scenarios....
using the hyper link coming from the user reset password link sent to user email..
eg: localhost/myApp/login/resetpassword//
Here I can authenticate userID and activationSecretCode in DB and let user reset password
user can click on resetpassword link from user settings page.
eg: Since the user is already coming from user settings page, I can validate userSession and allow him to reset password
User can login for first time successfully, but are forced to reset password due to admin requirements for reset initial default password.
eg: in this user neither have session, nor passing any activationcode to validate.
login method validates userid/default password and redirects to resetpassword mapping(method=GET).
How can the system authenticate the user request and allow him to reset password?
One alternative for this is, to use flash attributes and set a authenticationKey as flash attributes...which could be verified in resetpassword method.
is there other way to implement this....
Note: I posted an issue in implementing this approach in
Post: Spring: How to pass Java objects during redirect while using ModelAttribute
Any help?
I think the best way to implement this is using three different action methods:
resetPassword (e-mails)
resetLoggedUserPassword (via settings)
changeDefaultPassword
They may even share the same view, but the behaviors are not equal, so I would avoid overloading the action responsibility.
EDIT: elaborating on your comment:
1) To secure the e-mail link, one way is to add a authentication token. The token can be as weak as a hashed user id plus some salt string, or as strong as a GUID with expiration time in a database table, generated whenever a user requests a password reset.
2) The settings way is not a problem, considering that the user is already logged in.
3) The temporary password action can be secured the same way as 1, or the same way as 2, if you put the user on the session. Logging in the user even with the default password status shouldn't be a concern if the code that verify the status of the account are inside a request filter.

Resources