Laravel 8 reset password in PWA - laravel

I am trying to establish a password reset system in a Laravel + SPA environment with Vuejs. Except for the form that starts the process ('I have forgotten my password'), the rest of the actions I want to be carried out in the standard Laravel way, outside the spa and using Laravel Auth. So I have:
web.php
//This sends a custom email notification.
Route::post('/forgot-password', 'UserController#reestablecerPassword')->middleware('guest')->name('password.email');
//Shows the standard view auth.passwords.reset, when we can reset password values.
Route::get('/reset-password/{token}', function ($token) {
return view('auth.passwords.reset', ['token' => $token]);
})->middleware('guest')->name('password.reset');
//Route to custom method in order to receive and process the previous form
Route::post('/reset-password', 'UserController#procesarReestablecerPassword')->middleware('guest')->name('password.update');
//this returns a simple view where it is reported that the password has been changed
Route::get('/changedpassword', function () {
return view('changedpassword');
})->name('changedpassword');
Methods in UserController.php
//Handles the response by email and the response to the front-end to inform that the email has been sent
public function reestablecerPassword(Request $request){
$request->validate(['email' => 'required|email']);
$status = Password::sendResetLink(
$request->only('email')
);
Log::info('status:');
Log::info($status);
$status == 'passwords.sent' ? $respuesta = response('OK', 200) : $respuesta = response('KO', 400);
return $respuesta;
}
//Process the request that contains the form data and reset the password. The program flow does not execute this method in production, the log is not written
public function procesarReestablecerPassword(Request $request) {
Log::info('entra a procesarReestablecerPassword con estos valores en la petición:');
Log::info($request);
$request->validate([
'token' => 'required',
'email' => 'required|email',
'password' => 'required|min:8|confirmed',
]);
$status = Password::reset(
$request->only('email', 'password', 'password_confirmation', 'token'),
function ($user, $password) use ($request) {
$user->forceFill([
'password' => Hash::make($password)
])->save();
$user->setRememberToken(Str::random(60));
event(new PasswordReset($user));
}
);
return $status == Password::PASSWORD_RESET
? redirect()->route('changedpassword')->with('status', __($status))
: back()->withErrors(['email' => [__($status)]]);
}
The issue appears when we submit the reset-password view form, when I get the error:
The GET method is not supported for this route. Supported methods: POST.
Observing the operation in the web browser:
In the local environment, where the operation is correct, two actions happen after pressing the submit button of the form:
A first POST type is sent with the form data. Such as:
https://appname.com/reset-password
A 'Location' header is received to redirect to a second URL via GET that includes the user token. Being of type:
https://appname.com/reset-password/jkladjfñl9iu08adDjfjnnakRfpaiw
Well, in the production environment, this token is not found in the url received in the Location header (I don't know why this occurs)
Thanks in advance.

Related

Laravel test fails, but works on postman

I have a test for a user logging out and having their token deleted.
use RefreshDatabase;
public function setUp() :void {
parent::setUp();
\Artisan::call('migrate',['-vvv' => true]);
\Artisan::call('passport:install',['-vvv' => true]);
\Artisan::call('db:seed',['-vvv' => true]);
}
...
/**
* #test
*/
public function a_user_has_tokens_removed_when_logged_out()
{
// login
$this->withoutExceptionHandling();
$user = factory('App\User')->create();
$response = $this->post('/api/login', [
'username' => $user->email,
'password' => 'password'
]);
$token = json_decode($response->getContent())->access_token;
$this->assertTrue(!$user->tokens->isEmpty());
// logout
Passport::actingAs($user, ['*']);
$logout = $this->json('POST', 'api/logout')->withHeaders([
'Accept' => 'application/json',
'Authorization' => 'Bearer ' . $token
]);
$this->assertTrue($user->tokens->isEmpty());
}
First I'm creating a user and logging them in so a token is created and related to their user account.
I'm asserting that the token exists after hitting the login route, which passes.
Then I'm calling the logout route which will delete all the tokens the user has:
public function logout() {
auth()->user()->tokens()->each(function($token, $key) {
$token->delete();
});
return response()->json('Logged out successfully', 200);
}
routes/api.php
Route::middleware('auth:api')->post('logout', 'AuthController#logout');
This assertion on the test above is failing:
$this->assertTrue($user->tokens->isEmpty());
If I do a dd($user->tokens); before the assertion to check what's going on, the token shows up - it still exists.
But If I hit this api/logout route with Postman, which has everything stored in MySQL, all the tokens are being deleted successfully.
I don't understand what's going on and why this test is failing. Or rather, I don't understand why the $token->delete() doesn't work on the test, but does via Postman. What's different?
Before executing the assert, reload the user model relations via $user->fresh(), to ensure the deleted relations are reflected in the instance.
I don't know why, but within the testing context, this is not done automatically.

Why Auth::check() returns true after logout ? Passport

So I made Authentication using passport, everything worked fine until I logged user out. My paths are protected via auth:api guard so after logging out I can't access any functions, however my frontend is rendered via react based on Auth:check() value and it stays true after logging out. Therefore I am able to get into admin dashboard without any permissions, which is a bug and I can't find a solution to fix it.
This is my log out function:
public function logout()
{
if (Auth::check()) {
DB::table('oauth_access_tokens')
->where('user_id', Auth::user()->id)
->update([
'revoked' => true
]);
return response(['check' => Auth::check()]); // I get true after logging out
}
return response(['check' => Auth::check()]);
}
This is my login and register functions:
public function register(Request $request){
$validatedData = $request->validate([
'name' => 'required|max:55|unique:users',
'password' => 'required'
]);
$validatedData['password'] = bcrypt($request->password);
$user = User::create($validatedData);
$accessToken = $user->createToken('authToken')->accessToken;
return response()
}
public function login(Request $request)
{
$loginData = $request->validate([
'name' => 'required',
'password' => 'required'
]);
$a = auth()->attempt($loginData, true);
if(!$a) {
return response(['message'=>'Invalid credentials');
}
$accessToken = auth()->user()->createToken('authToken')->accessToken;
return response()->json($accessToken);
}
What have I missed?
The reason that Auth::check() returns true is the user is set on the auth service. You are only revoking the access token, meaning that the user will be logged out from the next request.
You can solve this one of two ways
1) Assume that the any call to the logout route will result in the user being logged out, irrespective of the logic performed. For example, you could make the call and then clear the access token in your frontend (or perform whatever other logout logic).
2) You can call Auth::logout() in your code, which will set the current user on the authentication service to null resulting in Auth::check() returning false.

LARAVEL & VUE: How can I get the API_TOKEN of the logged in user with an API request?

I have a SPA using VUE and LARAVEL 5.8
I have setup an API_TOKEN associated to the logged in user. Everything works fine right after the login. I get the API_TOKEN, I save it into a var and I send it together with the Axios request. In Laravel I have a middleware that is taking care of the token and comparing it with the one setup on the logged in user.
the problem though occur when session expires. Because I still can navigate the private pages and make API requests to save and delete content. This is possible I think because I still have the same API_TOKEN saved in the var and the middleware apparently doesn't get that the session is expired.
So I want to obtain the API_TOKEN every time I'm doing an Ajax, request so when the session expires, I won't get the token and therefore, I won't be able to complete the request.
This is my setup.
web.php is where I have the only php route that points to a singlePageController:
Auth::routes();
Route::get('/{any}', 'SinglePageController#index')->where('any', '.*');
Then in the singlePageController I return the view:
class SinglePageController extends Controller
{
public function index() {
return view('app', ['loggedUser' => auth()->user()]);
}
}
Then I have the api.php where I have the API routes. As you can see at the end I have the middleware to make it private. Just to make an example this is the one I use for updating the content:
Route::put('event/update/{slug}', 'EventController#update')->middleware('auth:api');
Then the related controller of that API route:
public function update(Request $request, $slug)
{
$event = Event::where('slug', $slug)->first();
$event->title = $request->input('title');
return new EventResource($event);
}
And in the end this is the Resource I use to define what and how the API data is going to be displayed:
public function toArray($request)
{
// return parent::toArray($request);
return [
'id' => $this->id,
'title' => $this->title,
'slug' => $this->slug,
'curator' => $this->curator,
'featured_image' => $this->featured_image,
'body' => $this->body,
'date' => $this->date
];
}
So this above is the flow I have. Then when I do an axios call to update the content, I'm doing something like:
axios({
method: 'PUT',
url: '/api/event/update/' + this.$route.params.slug + '?api_token=' + this.isLogged.apiToken,
data: dataToSave,
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
}
})
.then((response) => {
this.getNotification('Success: The Event has been saved');
})
.catch((error) => {
this.getNotification('Error: Impossible saving the event');
console.log(error);
})
Do you know how to make it? or if there is a better way to accomplish that?
you and do like, your login method should like this.
public function login(Request $request)
{
if (Auth::attempt(['email' => $request['email'], 'password' => $request['password']])) {
$user = Auth::user();
$success = $user->createToken(config('app.name'))->accessToken;
return response()->json(["token" => $success, 'status' => 200]);
} else {
return response()->json(['message' => "Email or Password do not match"], 401);
}
}

How-to Form confirmation response

I am a Laravel newbie. I have gone through tutorials successfully...this is my first production app for a client, using Laravel
I have a form and am able to get the submitted data into a database. The user is given a generic error though. After successful submission, I cannot redirect the user to a confirmation page. Any help is appreciated.
Here is the code from my controller (the store function):
/**
* Store a newly created resource in storage.
*
* #return Response
*/
public function store()
{
$rules = array(
'lastname' => 'required',
'email' => 'required|email',
'phone' => 'required',
'date' => 'date|date_format:"m/d/Y"'
);
$validator = Validator::make(Input::all(), $rules);
// process the login
if ($validator->fails()) {
return Redirect::to('nerds/create')
->withErrors($validator)
->withInput(Input::except('password'));
}
else
{
// store
$registration = new Registration;
$registration->firstname = Input::get('firstname');
$registration->lastname = Input::get('lastname');
$registration->email = Input::get('email');
$registration->date = date("Y-m-d", strtotime(Input::get('date')));
$registration->phone = Input::get('phone');
$registration->venue = Input::get('venue');
$registration->venueCity = Input::get('venueCity');
$registration->save();
// redirect
Session::flash('message', 'Successfully submitted, someone will contact you soon!');
return Redirect::to('thankyou');
}
}
Please make sure the app/storage folder has the full permissions. Apache should be able to write to that folder.
There must be a route to handle the 'Redirect::to'
The tutorial I followed failed to mention this fact.
Along with the code from my controller's store() function, the following route is necessary.
Route::get('/registration/thankyou', function()
{
return View::make('thankyou');
});

Laravel: Auth Attempt failed

I have tried the following codes. But the auth attempt failed.
// validate the info, create rules for the inputs
$rules = array(
'email' => 'required|email', // make sure the email is an actual email
'password' => 'required|alphaNum|min:3' // password can only be alphanumeric and has to be greater than 3 characters
);
// run the validation rules on the inputs from the form
$validator = Validator::make(Input::all(), $rules);
// if the validator fails, redirect back to the form
if ($validator->fails()) {
return Redirect::to('login')
->withErrors($validator) // send back all errors to the login form
->withInput(Input::except('password')); // send back the input (not the password) so that we can repopulate the form
} else {
// create our user data for the authentication
$userdata = array(
'Email' => Input::get('email'),
'Password' => Input::get('password')
);
// attempt to do the login
if (Auth::attempt($userdata)) {
// validation successful!
// redirect them to the secure section or whatever
// return Redirect::to('secure');
// for now we'll just echo success (even though echoing in a controller is bad)
echo 'SUCCESS!';
} else {
// validation not successful, send back to form
return Redirect::to('login')->with('message', 'Login Failed');
}
}
I have the following columns in Users table.
id,
Username,
Email,
created_at,
updated_at,
Password
I have changed the table name in model. Please advice. What's wrong?
Usually the convention is to make database columns lowercase. With the email column, that should not be a problem. As the documentation states, the 'email' field is only used as an example. http://laravel.com/docs/security#authenticating-users
To make Laravel use your capitalised password column. Open your user eloquent model and change:
public function getAuthPassword()
{
return $this->password;
}
to:
public function getAuthPassword()
{
return $this->Password;
}
Pretty sure that'll do the trick,

Resources