Search code examples
laravellaravel-5.5progressive-web-appslaravel-passport

Logging Laravel user in to progress web app using API call


I've setup Passport routes to login users via an API for a mobile app. However, part of the mobile app use a webview to display gated content and part of it pull from the API for other content.

Once a user logs into the app using the API, I need them to also be logged into the web content at the same time.

However, there is no session created with the API. How can I both login and logout users thru the API in a way that will carryover when displaying a webview?

I've tried using this on my API\LoginController.php to perform the login:

protected function sendLoginResponse(Request $request)
{
    if ($request->hasSession()) {
        $request->session()->regenerate();
    } else {
        // Login user for PWA pages.
        \Session::start();
        \Auth::login($this->guard()->user());
    }
    $this->clearLoginAttempts($request);
    return $this->authenticated($request, $this->guard()->user());
}

protected function authenticated(Request $request, User $user): ?JsonResponse
{
    return response()->json(['token' => $user->createToken(config('app.name'))->accessToken], 200);
}

This extends the base Laravel default LoginController.php but overrides these methods to support JSON responses.

The associated route:

Route::post('login')->name('api.auth.login')->uses('API\\Auth\\LoginController@login');

Login works fine when called thru the API but does not persist the session into the webview.


Solution

  • Solved this using Hypnopompia's solution in this GitHub issue: https://github.com/laravel/passport/issues/135

    Extracted his code and simply inserted it into a web middleware.

    namespace App\Http\Middleware;
    
    use Auth;
    use Closure;
    use DB;
    use Laravel\Passport\Passport;
    use Lcobucci\JWT\Parser;
    use Lcobucci\JWT\Signer\Rsa\Sha256;
    use Lcobucci\JWT\ValidationData;
    
    class ApiTokenWebLogin
    {
        /**
         * @param string $tokenId
         *
         * @return mixed
         */
        private function isAccessTokenRevoked($tokenId)
        {
            return DB::table('oauth_access_tokens')
                ->where('id', $tokenId)
                ->where('revoked', 1)
                ->exists();
        }
    
        /**
         * @param string $jwt
         *
         * @return array|bool
         */
        private function validateToken($jwt)
        {
            try {
                $token = (new Parser())->parse($jwt);
    
                if ($token->verify(new Sha256(), file_get_contents(Passport::keyPath('oauth-public.key'))) === false) {
                    return false;
                }
    
                // Ensure access token hasn't expired.
                $data = new ValidationData();
                $data->setCurrentTime(time());
    
                if ($token->validate($data) === false) {
                    return false;
                }
    
                // Check if token has been revoked.
                if ($this->isAccessTokenRevoked($token->getClaim('jti'))) {
                    return false;
                }
    
                return [
                    'user_id' => $token->getClaim('sub'),
                ];
            } catch (\Exception $e) {
                return false; // Decoder error.
            }
        }
    
        /**
         * Handle an incoming request.
         *
         * @param  \Illuminate\Http\Request $request
         * @param  \Closure $next
         *
         * @return mixed
         */
        public function handle($request, Closure $next)
        {
            $token = $request->bearerToken();
    
            // If user passed a valid Passport token, then login to the webview.
            if (!empty($token) && $request->hasSession() && !Auth::check() && $user_id = $this->validateToken($token)) {
                \Auth::loginUsingId($user_id);
            }
    
            return $next($request);
        }
    }