Better way to include custom fields from user table in JWT claim in laravel - laravel

I am using tymon/jwt-auth liibrary for JWT authentication in my project which is built using laravel.
In my mysql users table I am having a role_id column. I want to include that in my generated JWT token's claim on login so that I can implement role based ACL using my JWT token on subsquent requests.
Below is what I have tried to get it working
$credentials = $request->only('email', 'password');
$userModel = User::where('email',$request->input('email'))->get()->first();
$role = $userModel->roles_id;
$customClaim = ['role' => $role];
try
{
// attempt to verify the credentials and create a token for the user
if (! $token = JWTAuth::attempt($credentials, $customClaim))
{
//return 401 error
}
}
catch (JWTException $e)
{
//return 500 error
}
return response()->json(compact('token'));
}
Above code is working fine as expected but problem with above code is on login I have to hit my database twice once for getting role and one for authenticating user. That would be great if someone can suggest a better way to do it in a single database hit.

Related

How to make login as other user in API using laravel passport?

I am using laravel passport for API authentication. and I want to log in as different users with different roles from superadmin. So how can I achieve this? Please give your suggestions.
public function masqueradeNotary($profileId)
{
$userId = decodeC($profileId);
$notaryUser = $this->adminService->getUser($userId);
if($userId){
//logout from current login user
$user = auth()->user()->token();
$user->revoke();
//login as notary user
$userRoles = $notaryUser->roles()->get();
// $scopes = [];
// if ($userRoles) {
// $scopes = Arr::pluck($userRoles,'code');
// }
if(Auth::login($notaryUser)){
\Log::info("auth user");
\Log::info(auth()->user());
// $token = $user->createToken($user->email . '-' . now(), $scopes);
}
}
}
Welcome to stackoverflow.
Well, you should look at spatie's package, it might make your life easier.
You can apply roles on the registration if you create two different registration functions. In the front-end, you have to somehow make the user decide and pass that value (a checkbox would be ideal).
I got the solution. there is no need to check auth login just log out the current user and revoke the access token and create a token for the user directly.
$token = $user->createToken($user->email . '-' . now(), $scopes);

Test Passport's Authorization code grant authentication flow

Any idea on how i can test my authentication routes in authorization code grant:
- GET: '/oauth/authorize/?' . $query
- POST: 'oauth/token'
The problem is that according to the docs you need to provide a redirect_uri field in your query and i don't know how you suppose to have one in tests and then get the response from your laravel app.
i don't want to test this api with my frontend app.(if possible)
i haven't showed any code bc i just need a general idea of the testing process of such APIs that are working with clients and redirect_uris
on google i found tests around password grant authentication which doesn't need a redirect_uri field
this is what i tryed and it failed.
test:
$user = User::orderBy('id', 'asc')->first();
$token = $user->createToken('personal_access');
Passport::actingAs($user, [], 'api');
(new AuthController)->logout();
if (($user = Auth::user()->toArray()) !== null) {
dd(1, $user);
} else {
dd(0);
}
Auth::user() returns the $user
AuthController:
public function logout(): Response
{
$tokenId = $this->getTokenId();
$tokenRepository = app(TokenRepository::class);
$tokenRepository->revokeAccessToken($tokenId);
$refreshTokenRepository = app(RefreshTokenRepository::class);
$refreshTokenRepository->revokeRefreshTokensByAccessTokenId($tokenId);
Artisan::call('passport:purge');
return response('Successfully loged you out.', 200);
}
private function getTokenId(): int
{
return (new CheckAuthentication)->getAuthenticated()->token()->id;
}
$tokenId is always zero.

how to check if user is authenticated with passport (get user from token using laravel-passport)

I am using Passport to log in users to a Laravel API endpoint, users get authenticated using their social accounts (google, facebook) using laravel-socialite package.
the workflow of logging users in and out works perfectly (generating tokens...Etc). The problem is I have a controller that should return data based on whether there is a user logged in or not.
I do intercept the Bearer token from the HTTP request but I couldn't get the user using the token (I would use DB facade to select the user based on the token but I am actually looking whether there is a more clean way already implemented in Passport)
I also don't want to use auth:api middleware as the controller should work and return data even if no user is logged in.
this is the api route:
Route::get("/articles/{tag?}", "ArticleController#get_tagged");
this is the logic I want the controller to have
public function get_tagged($tag = "", Request $request)
{
if ($request->header("Authorization"))
// return data related to the user
else
// return general data
}
Assuming that you set your api guard to passport, you can simply call if (Auth::guard('api')->check()) to check for an authenticated user:
public function get_tagged($tag = "", Request $request)
{
if (Auth::guard('api')->check()) {
// Here you have access to $request->user() method that
// contains the model of the currently authenticated user.
//
// Note that this method should only work if you call it
// after an Auth::check(), because the user is set in the
// request object by the auth component after a successful
// authentication check/retrival
return response()->json($request->user());
}
// alternative method
if (($user = Auth::user()) !== null) {
// Here you have your authenticated user model
return response()->json($user);
}
// return general data
return response('Unauthenticated user');
}
This would trigger the Laravel authentication checks in the same way as auth:api guard, but won't redirect the user away. In fact, the redirection is done by the Authenticate middleware (stored in vendor/laravel/framework/src/Illuminate/Auth/Middleware/Authenticate.php) upon the failure of the authentication checking.
Beware that if you don't specify the guard to use, Laravel will use the default guard setting in the config/auth.php file (usually set to web on a fresh Laravel installation).
If you prefer to stick with the Auth facade/class you can as well use Auth::guard('api')->user() instead or the request object.
thanks to #mdexp answer
In my case I can resolve my problem with using
if (Auth::guard('api')->check()) {
$user = Auth::guard('api')->user();
}
In my controller.

Do I need to store the FB access token on the backend in my Laravel Socialite+JWT and Ionic app?

I am slightly confused about the correct flow to Register and Login a user with cordova-plugin-facebook4 and Laravel Socialite + tymondesigns/jwt-auth. My front end is in Ionic 2 and the backend is in Laravel 5.4
My flow: (heavily borrowed from this blog post)
Using the cordova-plugin-facebook4 , perform a FB.login(). This returns the following json
{
status: "connected",
authResponse: {
session_key: true,
accessToken: "EAACgZAhHOaPsBAIjUUASeKSLnbEkashdkashdkhakdhakdh",
expiresIn: 5183979,
sig: "...",
secret: "...",
userID: "634565435"
}
}
Post the "accessToken" to http:///auth/facebook
Retrieve the user profile using the Socialite "userFromToken" method.
$profile = Socialite::driver('facebook')->userFromToken($token);
Check if the user exists in the DB.
IF it does create and return a new access token using JWTAuth
ELSE create a new user in the DB and return a new access token using JWTAuth
$provider = "facebook"
try{
$profile = Socialite::driver($provider)->userFromToken($token);
}
catch(Exception $exception)
{
return response()->json([
'error'=>'User retrieval failed from provider'
],401);
}
//Check if the user registered earlier with this provider.
try{
$existingUser = User::where('provider','=',$provider)
->where('provider_id','=',$profile->getId())
->firstOrFail();
//If user is found and no exception was raised
if ($existingUser)
{
return response()->json([
'token'=>JWTAuth::fromUser($existingUser),
'user'=>$existingUser
]);
}
}
catch (ModelNotFoundException $exception)
{
$user=new User();
$user->email=$profile->getEmail();
$user->name=$profile->getName();
$user->password=$this->random_password();
$user->provider=$provider;
$user->provider_id=$profile->getId();
$user->save();
return response()->json([
'token'=>JWTAuth::fromUser($user),
'user'=>$user
]);
}
My Confusion:
Are the "Login with Facebook" and "Register/Signup with Facebook" two exclusive functions? How would subsequent logins work for a mobile app after the user registers on Laravel the first time?
In Step 4 when I check if the user exists in the backend I'm querying the table on the "provider_id" returned by Socialite::driver($provider)->userFromToken($token)->getId(). I don't understand how this value is unique to a particular user.
In both cases for step 2 - a new JWT Auth token is created from the user and returned to the front end. I plan to save this token on the front end and use it for protected resource access. However, I'm not sure if I need to store the FB Access_Token in the DB and share it with the front end to be cached as well.
How will the login process work when the app is reopened. The user should be auto logged in but would this happen via Facebook, via Laravel Social or just the locally stored JWT?
Thanks
Sam

Using laravel socialite and jwt-auth without session

Short version: What would be the appropriate way to send the JWT generated from Facebook login (laravel/socialite) to the angularjs front end without using session.
Long Version
I am making an app that has angularjs front end and laravel 5.2 backend. I am using tymondesigns/jwt-auth for authentication instead of session.
I am also using laravel/socialite for social Facebook authentication. For that I am using the stateless feature of socialite so that I don't need session in any ways.
The basic authentication works perfectly. But, when I try to use Facebook login, I follow these steps
User clicks on a button on the angular side that redirects to the provider login page of the back end.
public function redirectToProvider() {
return Socialite::with('facebook')->stateless()->redirect();
}
2. User gives his login information. After logging in he is redirected to my handlecallback function.
try {
$provider = Socialite::with('facebook');
if ($request->has('code')) {
$user = $provider->stateless()->user();
}
} catch (Exception $e) {
return redirect('auth/facebook');
}
return $this->findOrCreateUser($user);
Next I use the findorcreate function to determine whether the user exists or not. If not than I just create a new user and create JWT from that.
$user = User::where('social_id', '=', $facebookUser->id)->first();
if (is_object($user)) {
$token = JWTAuth::fromUser($user);
return redirect()->to('http://localhost:9000/#/profile?' . 'token=' . $token);#angular
} else {
$result = array();
$result['name'] = $facebookUser->user['first_name']
$result['email'] = $facebookUser->user['email'];
$result['social_id'] = $facebookUser->id;
$result['avatar'] = $facebookUser->avatar;
$result['gender'] = $facebookUser->user['gender'];
$result['status'] = 'active';
$result['login_type'] = 'facebook';
$result['user_type'] = 'free_user';
try {
$user = User::create($result);
} catch (Exception $e) {
return response()->json(['error' => 'User already exists.'], HttpResponse::HTTP_CONFLICT);
}
$token = JWTAuth::fromUser($user);
return redirect()->to('http://localhost:9000/#/profile?' . 'token=' . $token);#angular
}
My problem is, in the last block of code I am having to send the jwt to my frontend via url. Which isn't secure at all. What would be the right way to send the generated JWT to the frontend without using session. Thank you
The official documentation of Laravel Socialite says:
Stateless Authentication
The stateless method may be used to disable session state verification. This is useful when adding social authentication to an API:
return Socialite::driver('google')->stateless()->user();
Then, you can authenticate using the jwt-auth method:
JWTAuth::fromUser($user)
If you're using $http on the Angular side, try returning the token as a JSON response from Laravel:
return response()->json(compact('token'));
Then store the token in localStorage or sessionStorage or what have you.
If you're generating your Angular page from within Laravel (i.e. not using Laravel as an API, but showing your Angular page from /public/index.php, for instance) you could load the view with the token in the data for the view.
As long as you're using HTTPS either of these two scenarios are better than passing the token in the redirect URL.
You can store token and use client side redirect without storing to browser history to redirect user to profile page without token in URL:
document.location.replace({profile-url})

Resources