Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Laravel 5.1/AngularJS: Reset password in Angular view (how to validate CSRF token?)

I managed to adjust the default Laravel auth so that it will work as an API for my AngularJS, and so far everything works well. Can go to /reset and enter an email and get sent an email with a password reset link which goes to /reset/{token} and if you don't get any validation errors, your password will successfully be changed.

The only problem is since I am using an Angular view, there isn't really anything validating the token and making sure it's not just gibberish before showing the reset-password state. I tried adding this to the top of the controller:

    if ($stateParams.token != $cookies.get('XSRF_TOKEN')) {
        $state.go('reset');
    }

...which would basically see if the token is the current CSRF token, but that doesn't work because when the password reset link is sent the CSRF token is changed or something... it is no longer the token from the session.

Anyone have any ideas how I can do this? I want to just redirect the user if the token entered in the url on `/reset/:token' is not valid.

Here is my code..

App.js:

        .state('reset', {
            url: '/reset',
            data: {
                permissions: {
                    except: ['isLoggedIn'],
                    redirectTo: 'user.dashboard'
                }
            },
            templateUrl: 'views/auth/forgot-password.html',
            controller: 'ForgotPasswordController as forgot'
        })
        .state('reset-password', {
            url: '/reset/:token',
            data: {
                permissions: {
                    except: ['isLoggedIn'],
                    redirectTo: 'user.dashboard'
                }
            },
            templateUrl: 'views/auth/reset-password.html',
            controller: 'ResetPasswordController as reset'
        })

This is in the ResetsPassword trait in ResetsPassword.php. Most was already set up but I removed/changed a lot to work as an API:

     /**
     * Send a reset link to the given user.
     */
    public function postEmail(EmailRequest $request)
    {
        $response = Password::sendResetLink($request->only('email'), function (Message $message) {
            $message->subject($this->getEmailSubject());
        });

        switch ($response) {
            case Password::RESET_LINK_SENT:
                return;

            case Password::INVALID_USER:
                return response()->json([
                    'denied' => 'We couldn\'t find your account with that information.'
                ], 404);
        }
    }

    /**
     * Get the e-mail subject line to be used for the reset link email.
     */
    protected function getEmailSubject()
    {
        return property_exists($this, 'subject') ? $this->subject : 'Your Password Reset Link';
    }

    /**
     * Reset the given user's password.
     */
    public function postReset(ResetRequest $request)
    {
        $credentials = $request->only(
            'password', 'password_confirmation', 'token'
        );

        $response = Password::reset($credentials, function ($user, $password) {
            $this->resetPassword($user, $password);
        });

        switch ($response) {
            case Password::PASSWORD_RESET:
                return;

            default:
                return response()->json([
                    'error' => [
                        'message' => 'Could not reset password'
                    ]
                ], 400);
        }
    }

    /**
     * Reset the given user's password.
     */
    protected function resetPassword($user, $password)
    {
        $user->password = bcrypt($password);

        $user->save();
    }
like image 788
Lansana Camara Avatar asked Dec 18 '15 02:12

Lansana Camara


People also ask

How to send password reset email in Laravel with token?

Create two laravel APIs, for making a reset password request other for resetting the password. Handle password reset request with the token, to restrain redundancy. Send password reset mail via mailtrap.io in laravel with a valid token.

How to handle Laravel API through angular service?

Handle laravel API through angular service with full-on consensus between backend and frontend. To comprehend or even start working on the pre-built repo, kindly clone the repo using the following command: The project simultaneously contains backend (Laravel) and frontend (Angular) folders, write your code accordingly.

How to add match password validation in Angular application?

I will give you full example of how to add match password validation in angular application. we will add two textbox with password and confirm password in angular using reactive form. we will create our custom ConfirmedValidator class for checking match validation. you can also see bellow preview for validation.

How to build secure Laravel API for secure user authentication?

User login and signup in laravel and angular. Building secure laravel API for secure user authentication using JWT token. Password hashing to store the password securely. Refresh token at a specific interval to add the security. To assimilate the entire authentication process, please read this whole article:


1 Answers

Figured this out.

For anyone having a similar problem... Here's how I solved it (will probably make it better later on, but for now it works).

I added another url for the API which is reset/password and it takes a GET request. I pass it the token based on the $stateParams value and if that token does not exist in the password_resets table OR if that token does exist and is expired, return some errors. In the controller, I handle the errors with a redirect. Again I don't think this is ideal because anyone looking at the source could change it up and remove the redirect so I have to find a better way to implement this.

But again, for now it works and it is a solution nonetheless.

ResetsPasswords.php (added a method for the get request):

public function verifyToken(Request $request)
    {
        $user = DB::table('password_resets')->where('token', $request->only('token'))->first();

        if ($user) {
            if ($user->created_at > Carbon::now()->subHours(2)) {
                return response()->json([
                    'success' => [
                        'message' => 'Token is valid and not expired.'
                    ]
                ], 200);
            } else {
                return response()->json([
                    'error' => [
                        'message' => 'Token is valid but it\'s expired.'
                    ]
                ], 401);
            }
        } else {
            return response()->json([
                'error' => [
                    'message' => 'Token is invalid.'
                ]
            ], 401);
        }
    }

and in my resetPasswordController.js I just check if the respose returns 'success' or any of the 'error' responses, and if it's an 'error' response I would just do something like $state.go('reset') which would redirect them back to the "forgot password" form where they enter their email to get a reset password link.

EDIT:

I figured that checking for a valid token in the controller was bad because it would always load the view at least for a split second. I was looking for some kind of middleware but then I forgot that I was already using the angular-permission package, which sort of acts as a front-end middleware.

I defined a role isTokenValid and set it up such that it automatically calls a function in the authService I have and gets a response from my API based on the validity of the token. If successful, the role allows the user to enter the state. Otherwise it redirects to the reset state. This prevents the view showing for the split second. Acts very similar to Laravel middleware.

Only problem is since it's happening on the front end, any hacker can bypass that but that's okay because the server-side code is still there so even if they access the view they can't do anything with the passwords they enter cause the token is still invalid and has to match a particular user.

Another improvement would be to find a way to disallow the view even without the front-end middleware implementation. Maybe I'll update this again if I find a way to do that.

Implementation

The role:

        .defineRole('isTokenValid', function(stateParams) {
            var token = stateParams.token;
            var deferred = $q.defer();

            authService.verifyToken(token)
                .then(function(res) {
                    if (res.success) {
                        deferred.resolve();
                    }
                }, function(res) {
                    if (res.error) {
                        deferred.reject();
                    }
                });

            return deferred.promise;
        });

And the state:

            .state('reset-password', {
                url: '/reset/:token',
                data: {
                    permissions: {
                        only: ['isTokenValid'],
                        redirectTo: 'reset'
                    }
                },
                templateUrl: 'views/auth/reset-password.html',
                controller: 'ResetPasswordController as reset'
            })

Hope it helps anyone with the same problem.

like image 191
Lansana Camara Avatar answered Oct 20 '22 01:10

Lansana Camara