ellaisys / aws-cognito

AWS Cognito package (with MFA Feature) using the AWS SDK for PHP/Laravel
https://ellaisys.github.io/aws-cognito/
MIT License
107 stars 42 forks source link

No Token Exception #36

Closed Atronix1902 closed 1 year ago

Atronix1902 commented 1 year ago

Hey guys, I am trying to build a simple login currently. The login is based on your demo application but I always get the error "No Token Exception", Unauthorized Request... I don't know what I am doing wrong though. The registration works just fine without any errors but the login works only if I don't use the middleware or try to get the Cognito user instead of the db user for example:

Route::get('/home', function (AwsCognito $cognito) {
    ddd($cognito->user());
    return view('index');
});

or

Route::middleware('aws-cognito')->get('/home', function () {
    //ddd($cognito->user());
    return view('index');
});

Cognito Config is unedited on my profile is a repository called finance_aws which can be used to rebuild my application as long as you add your .env file. Here's my code:

User.php:

<?php

namespace App\Http\Controllers;

use App\Models\User;
use Ellaisys\Cognito\Auth\AuthenticatesUsers;
use Ellaisys\Cognito\Auth\RegistersUsers;
use Ellaisys\Cognito\AwsCognitoClient;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Redirector;

class UserController extends Controller
{
    use RegistersUsers, AuthenticatesUsers;

    public function register(Request $request, AwsCognitoClient $client): Application|RedirectResponse|Redirector|JsonResponse
    {
        $collection = collect($request->all());
        $data = $collection->only(['email', 'username']);

        try {
            if ($this->createCognitoUser($data)) {
                $user = new User($collection->toArray());
                if(!$user->save()) {
                    $this->delete($collection->get('username'), $client);
                }

                return redirect(route('login'));
            }
        } catch (\Exception $e) {
            return response()->json(['code' => $e->getCode(), 'msg' => $e->getMessage()]);
        }

        return response()->json(['status' => 'error']);
    }

    public function delete(String $username, AwsCognitoClient $client) {
        $client->deleteUser($username);
        if(User::find($username)->delete()) return response()->json(['success' => true]);
        else response()->json(['success' => false]);
    }

    public function getPrivateInfo(String $username, AwsCognitoClient $client) {
        return $client->getUser($username);
    }
}

Kernel.php:

protected $routeMiddleware = [
    'auth' => \App\Http\Middleware\Authenticate::class,
    'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
    'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
    'can' => \Illuminate\Auth\Middleware\Authorize::class,
    'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
    'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
    'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
    'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
    'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
    'aws-cognito' => \Ellaisys\Cognito\Http\Middleware\AwsCognitoAuthenticate::class
];

config/app.php:

'providers' => [

    /*
     * Laravel Framework Service Providers...
     */
    Illuminate\Auth\AuthServiceProvider::class,
    Illuminate\Broadcasting\BroadcastServiceProvider::class,
    Illuminate\Bus\BusServiceProvider::class,
    Illuminate\Cache\CacheServiceProvider::class,
    Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class,
    Illuminate\Cookie\CookieServiceProvider::class,
    Illuminate\Database\DatabaseServiceProvider::class,
    Illuminate\Encryption\EncryptionServiceProvider::class,
    Illuminate\Filesystem\FilesystemServiceProvider::class,
    Illuminate\Foundation\Providers\FoundationServiceProvider::class,
    Illuminate\Hashing\HashServiceProvider::class,
    Illuminate\Mail\MailServiceProvider::class,
    Illuminate\Notifications\NotificationServiceProvider::class,
    Illuminate\Pagination\PaginationServiceProvider::class,
    Illuminate\Pipeline\PipelineServiceProvider::class,
    Illuminate\Queue\QueueServiceProvider::class,
    Illuminate\Redis\RedisServiceProvider::class,
    Illuminate\Auth\Passwords\PasswordResetServiceProvider::class,
    Illuminate\Session\SessionServiceProvider::class,
    Illuminate\Translation\TranslationServiceProvider::class,
    Illuminate\Validation\ValidationServiceProvider::class,
    Illuminate\View\ViewServiceProvider::class,

    /*
     * Package Service Providers...
     */

    /*
     * Application Service Providers...
     */
    App\Providers\AppServiceProvider::class,
    App\Providers\AuthServiceProvider::class,
    // App\Providers\BroadcastServiceProvider::class,
    App\Providers\EventServiceProvider::class,
    App\Providers\RouteServiceProvider::class,
    Barryvdh\Debugbar\ServiceProvider::class,
    Aws\Laravel\AwsServiceProvider::class,
    Ellaisys\Cognito\Providers\AwsCognitoServiceProvider::class,
],

'aliases' => [

    'App' => Illuminate\Support\Facades\App::class,
    'Arr' => Illuminate\Support\Arr::class,
    'Artisan' => Illuminate\Support\Facades\Artisan::class,
    'Auth' => Illuminate\Support\Facades\Auth::class,
    'Blade' => Illuminate\Support\Facades\Blade::class,
    'Broadcast' => Illuminate\Support\Facades\Broadcast::class,
    'Bus' => Illuminate\Support\Facades\Bus::class,
    'Cache' => Illuminate\Support\Facades\Cache::class,
    'Config' => Illuminate\Support\Facades\Config::class,
    'Cookie' => Illuminate\Support\Facades\Cookie::class,
    'Crypt' => Illuminate\Support\Facades\Crypt::class,
    'Date' => Illuminate\Support\Facades\Date::class,
    'DB' => Illuminate\Support\Facades\DB::class,
    'Eloquent' => Illuminate\Database\Eloquent\Model::class,
    'Event' => Illuminate\Support\Facades\Event::class,
    'File' => Illuminate\Support\Facades\File::class,
    'Gate' => Illuminate\Support\Facades\Gate::class,
    'Hash' => Illuminate\Support\Facades\Hash::class,
    'Http' => Illuminate\Support\Facades\Http::class,
    'Js' => Illuminate\Support\Js::class,
    'Lang' => Illuminate\Support\Facades\Lang::class,
    'Log' => Illuminate\Support\Facades\Log::class,
    'Mail' => Illuminate\Support\Facades\Mail::class,
    'Notification' => Illuminate\Support\Facades\Notification::class,
    'Password' => Illuminate\Support\Facades\Password::class,
    'Queue' => Illuminate\Support\Facades\Queue::class,
    'RateLimiter' => Illuminate\Support\Facades\RateLimiter::class,
    'Redirect' => Illuminate\Support\Facades\Redirect::class,
    // 'Redis' => Illuminate\Support\Facades\Redis::class,
    'Request' => Illuminate\Support\Facades\Request::class,
    'Response' => Illuminate\Support\Facades\Response::class,
    'Route' => Illuminate\Support\Facades\Route::class,
    'Schema' => Illuminate\Support\Facades\Schema::class,
    'Session' => Illuminate\Support\Facades\Session::class,
    'Storage' => Illuminate\Support\Facades\Storage::class,
    'Str' => Illuminate\Support\Str::class,
    'URL' => Illuminate\Support\Facades\URL::class,
    'Validator' => Illuminate\Support\Facades\Validator::class,
    'View' => Illuminate\Support\Facades\View::class,
    'Debugbar' => Barryvdh\Debugbar\Facades\Debugbar::class,
    'AWS' => Aws\Laravel\AwsFacade::class,
],

LoginController:

<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;

//use Illuminate\Foundation\Auth\AuthenticatesUsers; //Removed for AWS Cognito
use Ellaisys\Cognito\Auth\AuthenticatesUsers;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Redirector;

//Added for AWS Cognito

class LoginController extends Controller
{
    /*
    |--------------------------------------------------------------------------
    | Login Controller
    |--------------------------------------------------------------------------
    |
    | This controller handles authenticating users for the application and
    | redirecting them to your home screen. The controller uses a trait
    | to conveniently provide its functionality to your applications.
    |
    */

    use AuthenticatesUsers;

    /**
     * Where to redirect users after login.
     *
     * @var string
     */
    protected $redirectTo = '/';

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('guest')->except('logout');
    }

    /**
     * Authenticate User
     *
     * @throws \HttpException
     *
     * @return mixed
     */
    public function login(\Illuminate\Http\Request $request)
    {
        try {
            //Convert request to collection
            $collection = collect($request->all());

            //Authenticate with Cognito Package Trait (with 'web' as the auth guard)
            if ($response = $this->attemptLogin($collection, 'web')) {
                if ($response===true) {
                    $request->session()->regenerate();

                    return redirect(route('home'))->with('success', true);
                } else if ($response===false) {
                    // If the login attempt was unsuccessful you may increment the number of attempts
                    // to login and redirect the user back to the login form. Of course, when this
                    // user surpasses their maximum number of attempts they will get locked out.
                    //
                    //$this->incrementLoginAttempts($request);
                    //
                    //$this->sendFailedLoginResponse($collection, null);
                } else {
                    return $response;
                } //End if
            } //End if
        } catch(Exception $e) {
            Log::error($e->getMessage());
            return $response->back()->withInput($request);
        } //Try-catch ends

    } //Function ends

    public function logout(Request $request): Redirector|Application|RedirectResponse
    {
        auth()->guard('web')->logout();
        if($request->getSession()->invalidate()) {
            return redirect(route('app.index'))->with('success', true);
        } else {
            return redirect(route('app.index'))->with('success', false);
        }
    }
} //Class ends

WebRoutes:

<?php

use App\Http\Controllers\AuthController;
use App\Http\Controllers\UserController;
use Ellaisys\Cognito\AwsCognito;
use Ellaisys\Cognito\AwsCognitoClient;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Route;

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/
Route::middleware('web')->get('/', function (AuthController $controller, AwsCognito $cognito, Request $request) {
    $request->session()->start();
    return view('index');
})->name('app.index');

Route::middleware('aws-cognito')->get('/home', function (AuthController $controller, AwsCognito $cognito) {
    //ddd($cognito->user());
    return view('index');
});

Route::middleware('aws-cognito')->get('/privacy', function () {
    return view('privacy');
})->name('privacy');

Route::middleware('aws-cognito')->get('/impress', function () {
    return view('impress');
})->name('impress');

Auth::routes();

Route::get('/login', function () {
    return view('auth.login');
})->name('login');

Route::middleware('aws-cognito')->post('/password/change', function (Request $request, AuthController $controller) {
    return $controller->changePassword($request);
})->name('cognito.action.change.password');

Route::middleware('aws-cognito')->get('/password/change', function () {
    return view('auth.passwords.change');
})->name('cognito.form.change.password');

Route::get('/register', function () {
    return view('auth.register');
})->name('user.register');

Route::post('/register', function (Request $request, UserController $controller, AwsCognitoClient $client) {
    return $controller->register($request, $client);
});

ChangePasswordController:

<?php

namespace App\Http\Controllers\Auth;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;

use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Support\Facades\Validator;

use Ellaisys\Cognito\Auth\ChangePasswords as CognitoChangePasswords; //Added for AWS Cognito

use Exception;
use Illuminate\Validation\ValidationException;
use Ellaisys\Cognito\Exceptions\AwsCognitoException;
use Ellaisys\Cognito\Exceptions\NoLocalUserException;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Aws\CognitoIdentityProvider\Exception\CognitoIdentityProviderException;

class ChangePasswordController extends Controller
{
    /*
    |--------------------------------------------------------------------------
    | Confirm Password Controller
    |--------------------------------------------------------------------------
    |
    | This controller is responsible for handling password confirmations and
    | uses a simple trait to include the behavior. You're free to explore
    | this trait and override any functions that require customization.
    |
    */

    use CognitoChangePasswords;

    /**
     * Where to redirect users when the intended url fails.
     *
     * @var string
     */
    protected $redirectTo = '/';

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('auth');
    }

    /**
     * Action to update the user password
     *
     * @param  \Illuminate\Http\Request  $request
     */
    public function actionChangePassword(Request $request)
    {
        try
        {
            //Validate request
            $validator = Validator::make($request->all(), [
                'email'    => 'required|email',
                'password'  => 'string|min:8',
                'new_password' => 'required|confirmed|min:8',
            ]);
            $validator->validate();

            // Get Current User
            $userCurrent = auth()->guard('web')->user();

            if ($this->reset($request)) {
                auth()->guard()->logout();
                $request->session()->invalidate();

                return redirect(route('login'))->with('success', true);
            } else {
                return redirect()->back()
                    ->with('status', 'error')
                    ->with('message', 'Password updated failed');
            } //End if
        } catch(Exception $e) {
            $message = 'Error sending the reset mail.';
            if ($e instanceof ValidationException) {
                $message = $e->errors();
            } else if ($e instanceof CognitoIdentityProviderException) {
                $message = $e->getAwsErrorMessage();
            } else {
                //Do nothing
            } //End if

            return redirect()->back()
                ->with('status', 'error')
                ->with('message', $message);
        } //Try-catch ends
    }
}
amitdhongde commented 1 year ago

Hi There, The recent upgrade resulted in this breaking change. The team is working on the hotfix, and that shall be released within the next couple of days.

Atronix1902 commented 1 year ago

Oh, ok thats explains it. Thanks for the info I! I'm gonna wait then.

leaf988 commented 1 year ago

I have also been waiting for this fix.

amitdhongde commented 1 year ago

The new release v1.0.7 is pushed and should be available in a short while for download. This issue is also fixed in that release.