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