So I just want to add login with google feature on my working authentication web app (with Codeigniter Shield package). I've already create a login_google function on Login controller that extends LoginController from shield package like this :
LoginController
<?php
namespace App\Controllers;
use App\Controllers\BaseController;
use CodeIgniter\HTTP\RedirectResponse;
use CodeIgniter\Shield\Controllers\LoginController;
class Login extends LoginController
{
function __construct()
{
require_once __DIR__ . '/../../vendor/autoload.php';
$this->userModel = new \App\Models\UserModel();
$this->google_client = new \Google_Client();
$this->google_client->setClientId(getenv('OAuth2.clientID'));
$this->google_client->setClientSecret(getenv('OAuth2.clientSecret'));
$this->google_client->setRedirectUri('http://localhost:8080/login_google');
$this->google_client->addScope('email');
$this->google_client->addScope('profile');
}
public function loginView()
{
if (auth()->loggedIn()) {
return redirect()->to(config('Auth')->loginRedirect());
}
/** #var Session $authenticator */
$authenticator = auth('session')->getAuthenticator();
// If an action has been defined, start it up.
if ($authenticator->hasAction()) {
return redirect()->route('auth-action-show');
}
$data['google_button'] = "<a href='".$this->google_client->createAuthUrl()."'><img src='https://developers.google.com/identity/images/btn_google_signin_dark_normal_web.png' /></a>";
return view('login', $data);
}
public function loginAction(): RedirectResponse
{
// Validate here first, since some things,
// like the password, can only be validated properly here.
$rules = $this->getValidationRules();
if (! $this->validate($rules)) {
return redirect()->back()->withInput()->with('errors', $this->validator->getErrors());
}
$credentials = $this->request->getPost(setting('Auth.validFields'));
$credentials = array_filter($credentials);
$credentials['password'] = $this->request->getPost('password');
$remember = (bool) $this->request->getPost('remember');
/** #var Session $authenticator */
$authenticator = auth('session')->getAuthenticator();
// Attempt to login
$result = $authenticator->remember($remember)->attempt($credentials);
if (! $result->isOK()) {
return redirect()->route('login')->withInput()->with('error', $result->reason());
}
/** #var Session $authenticator */
$authenticator = auth('session')->getAuthenticator();
// If an action has been defined for login, start it up.
if ($authenticator->hasAction()) {
return redirect()->route('auth-action-show')->withCookies();
}
return redirect()->to(config('Auth')->loginRedirect())->withCookies();
}
public function login_google() {
$token = $this->google_client->fetchAccessTokenWithAuthCode($this->request->getVar('code'));
if (!isset($token['error'])) {
$this->google_client->setAccessToken($token['access_token']);
$this->session->set('access_token', $token['access_token']);
$google_service = new \Google\Service\Oauth2($this->google_client);
$data = $google_service->userinfo->get();
$userdata = array();
if ($this->userModel->isAlreadyRegister($data['id'])) {
$userdata = [
'first_name' => $data['givenName'],
'last_name' => $data['familyName'],
'email' => $data['email'],
'avatar' => $data['picture'],
];
$this->userModel->updateUserData($userdata, $data['id']);
} else {
$userdata = [
'first_name' => $data['givenName'],
'last_name' => $data['familyName'],
'email' => $data['email'],
'avatar' => $data['picture'],
'oauth_id' => $data['id'],
];
$this->userModel->insertUserData($userdata);
}
$this->session->set('LoggedUserData', $userdata);
} else {
$this->session->set("error", $token['error']);
return redirect('/register');
}
return redirect()->to('/profile');
}
}
UserModel like this :
UserMode
<?php
namespace App\Models;
use CodeIgniter\Model;
use CodeIgniter\Shield\Models\UserModel as ModelsUserModel;
class UserModel extends ModelsUserModel
{
protected $allowedFields = [
'username',
'status',
'status_message',
'active',
'last_active',
'deleted_at',
'gender',
'first_name',
'last_name',
'avatar',
'phone_number',
'full_address',
'oauth_id',
];
function isAlreadyRegister($authid){
return $this->db->table('users')->getWhere(['id'=>$authid])->getRowArray()>0?true:false;
}
function updateUserData($userdata, $authid){
$this->db->table("users")->where(['id'=>$authid])->update($userdata);
}
function insertUserData($userdata){
$this->db->table("users")->insert($userdata);
}
}
But everytime I clicked sign in with google button, it won't work (the interface for choosing google account to authenticate is worked) and always return to login page
am I missing something when combining CodeIgniter Shield with Google Oauth ? Anyone can help ? TIA
A new package has been created for OAuth with Shield package: https://github.com/datamweb/shield-oauth
You can use it instead of your own one.
Related
I made a controller "Login" to make token below and successfully, but I don't know how to catch that token for another controller
<?php
namespace App\Controllers;
use CodeIgniter\RESTful\ResourceController;
use CodeIgniter\API\ResponseTrait;
use App\Models\UserModel;
use Firebase\JWT\JWT;
class Login extends ResourceController
{
/**
* Return an array of resource objects, themselves in array format
*
* #return mixed
*/
use ResponseTrait;
public function index()
{
helper(['form']);
$rules = [
'email' => 'required|valid_email',
'password' => 'required|min_length[6]'
];
if (!$this->Validate($rules)) return $this->fail($this->validator->getErrors());
$model = new UserModel();
$user = $model->where("email", $this->request->getVar('email'))->first();
if (!$user) return $this->failNotFound('Email Tidak Ditemukan');
$verify = password_verify($this->request->getVar('password'), $user['password']);
if (!$verify) return $this->fail('wrong Password');
$key = getenv('TOKEN_SECRET');
$payload = [
// issue at : kapan token dibuat
'iat' => 1356999524,
// non before : kapan expired
'nbf' => 1357000000,
'uid' => $user['id'],
'email' => $user['email'],
];
$token = JWT::encode($payload, $key, 'HS256');
return $this->respond($token);
// return redirect()->to(base_url('/me', $token));
}
}
I expect to know how the way to passing token from one controller to another
The token can't be passed to a different controller. Instead, the client (I'm assuming you're working in a API) should send the token as a parameter or as a header. The most common cases use Bearer Tokens
I am using laravel version 5.4.36 and i am using Laravel Sociallite plugin to connect user with facebook.
I want to get uploaded pictures of logged in user as suggested in the document This is available through the photos edge on the User object.
public function redirect()
{
// return Socialite::driver('facebook')->redirect();
return Socialite::driver('facebook')->fields([
'first_name', 'last_name', 'email', 'gender', 'birthday','location','hometown','age_range','friends','posts','photos'
])->scopes([
'email', 'user_birthday','user_gender','user_location','user_hometown',
'user_age_range','user_friends','user_link','user_photos','user_posts',
'user_tagged_places','user_videos','user_likes',
])->redirect();
}
In above code, I have requested
user_photo as scope and photos as fields
my callback function is
public function callback(SocialFacebookAccountService $service)
{
$user = $service->createOrGetUser(Socialite::driver('facebook')->fields([
'name', 'email', 'birthday','gender','location','hometown','age_range','friends','link',
'photos','posts','tagged_places','videos','likes',
])->user());
auth()->login($user);
return redirect()->to('/home');
}
where i have requested photos still i am not able to get photo age when i do
namespace App\Services;
use App\SocialFacebookAccount;
use App\User;
use Laravel\Socialite\Contracts\User as ProviderUser;
use File;
class SocialFacebookAccountService
{
public function createOrGetUser(ProviderUser $providerUser)
{
echo "<pre>"; print_r($providerUser);die;
$account = SocialFacebookAccount::whereProvider('facebook')
->whereProviderUserId($providerUser->getId())
->first();
if ($account) {
return $account->user;
} else {
$account = new SocialFacebookAccount([
'provider_user_id' => $providerUser->getId(),
'provider' => 'facebook'
]);
$user = User::whereEmail($providerUser->getEmail())->first();
// echo "<pre>";print_r($user);die;
if(!empty($providerUser->getAvatar())){
$fileContents = file_get_contents($providerUser->getAvatar());
File::put(public_path() . '/uploads/profile/' . $providerUser->getId() . ".jpg", $fileContents);
}
$imageUrl = $providerUser->getId() . ".jpg";
// echo "<pre>";print_r($providerUser->user);die;
if (!$user) {
$user = User::create([
'email' => $providerUser->getEmail(),
'name' => $providerUser->getName(),
'password' => md5(rand(1,10000)),
'avatar'=> $imageUrl,
'birthday' => $providerUser->user['birthday'],
'gender' => $providerUser->user['gender'],
'age_range' =>$providerUser->user['age_range']['min'],
'total_friends'=>$providerUser->user['friends']['summary']['total_count'],
'profile_link'=>$providerUser->user['link'],
]);
}
$account->user()->associate($user);
$account->save();
return $user;
}
}
}
Have print in the starting of the createOrGetUser function but still i am not able to get photo age under the user object.
Any suggestions please.
I wonder how to write proper unit test for my email sending method. It's a problem because inside method I get data from Auth object. Should I send id of user in Request?
public function sendGroupInvite(Request $request){
foreach ($request->get('data') as $item){
$invitations = new \App\Models\Invitations();
$invitations->user_id = Auth::id();
$invitations->name = $item["name"];
$invitations->email = $item["email"];
$invitations->status = 0;
$invitations->token = \UUID::getToken(20);
$invitations->username = Auth::user()->name;
$invitations->save();
$settings = UserSettings::where('user_id', Auth::id())->first();
$email = $item["email"];
$url = 'https://example.com/invite/accept/'.$invitations->token;
$urlreject = 'https://example.com/invite/reject/'.$invitations->token;
$mailproperties = ['token' => $invitations->token,
'name' => $invitations->name,
'url' => $url,
'email' => $email,
'urlreject' => $urlreject,
'userid' => Auth::id(),
'username' => Auth::user()->name,
'user_name' => $settings->name,
'user_lastname' => $settings->lastname,
'user_link' => $settings->user_link,
];
$this->dispatch(new SendMail(new Invitations($mailproperties)));
}
return json_encode(array('msg' => 'ok'));
}
I'm using Auth to get username and user id. When I testing it it's not works, because Auth it's null.
I would go with mocking the queue, something similar to this. Mock Documentation
class MailTester extends TestCase{
/**
* #test
*/
public function test_mail(){
Queue::fake();
// call your api or method
Queue::assertPushed(SendMail, function(SendMail $job) {
return $job->something = $yourProperties;
});
}
You could try "acting as" to deal with the Auth::user().
...
class MyControllerTest extends TestCase{
/**
* #test
*/
public function foo(){
$user = App\Users::find(env('TEST_USER_ID')); //from phpunit.xml
$route = route('foo-route');
$post = ['foo' => 'bar'];
$this->actingAs($user)//a second param is opitonal here for api
->post($route, $post)
->assertStatus(200);
}
}
I have a weird problem with my Laravel application, whereby this code gets called twice once the validation rules kick in. I have abstracted validation logic into a separate class, but no matter how I consume the API (tried using Postman, and with jQuery) it still appears to run twice with the output looking like this:
called{"email":["The email has already been taken."],"country":["The country must be a number."]}called{"email":["The email has already been taken."],"country":["The country must be a number."]}
I am only expecting one JSON response. I'm tearing my hair out, tried on two different connections and can't seem to work out why the custom request is called twice. This is a new Laravel app, so there isn't much code to conflict with it.
//Create User Request extends standard request. Handles Validation
public function __construct(CreateUserRequest $request){
$this->request = $request;
}
public function register()
{
try{
$array = DB::transaction(function(){
$email = $this->request->input('email');
$password = $this->request->input('password');
$companyName = $this->request->input('companyName');
$userName = $this->request->input('name');
$country = $this->request->input('country');
$company = Company::create([
'name' => $companyName,
'active'=>true,
'country_id'=>$country
]);
$user = User::create([
'company_id' => $company->id,
'name'=>'admin',
'email' => $email,
'password' => $password,
'active' =>true
]);
if( !$company || !$user )
{
throw new \Exception('User not created for account');
}
return compact('company', 'user');
});
$token = JWTAuth::fromUser($array['user']);
return Response::json(compact('token'));
}
catch( Exception $e )
{
return Response::json(['error' => $e->getMessage() ], HttpResponse::HTTP_CONFLICT );
}
}
Then the validation custom Request..
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Contracts\Validation\Validator;
use Response;
class CreateUserRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
public function response(array $errors)
{
// return Response::json(['errorg' => $errors ], 200 );
echo('called');
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'email' => 'required|unique:users',
'password' => 'required',
'companyName' => 'required',
'name' => 'required',
'country' => 'required|numeric'
];
}
}
Interesting.
Try to remove CreateUserRequest $request parameter from __construct() and add it to your register() method like this: register(CreateUserRequest $request). And use your request by calling $request instead of $this->request.
I am using Socialite to login/register with facebook into my application. When I dump my $facebookUser variable I see this json :
$facebookuser :
But when I try to store the id and avatar , it doesn't store it and I can't display the users profile picture of facebook. I am using laravel to store my user.
AuthController.php :
public function handleProviderCallback()
{
try {
$user = Socialite::driver('facebook')->user();
} catch (Exception $e) {
return redirect('auth/facebook');
}
$authUser = $this->findOrCreateUser($user);
Auth::login($authUser, true);
return redirect()->route('home')->with('successfullFacebookLogin', Auth::user()->name);
}
private function findOrCreateUser($facebookUser)
{
// When I dd($facebookuser) it gives json stated above
$authUser = User::where('facebook_id', $facebookUser->id)->first();
if ($authUser){
return $authUser;
}
return User::create([
'name' => $facebookUser->name,
'email' => $facebookUser->email,
'facebook_id' => $facebookUser->user['id'],
'avatar' => $facebookUser->avatar,
'facebookAccount' => 1
]);
}
Use Laravel Socialite provided methods to access user details rather than access the property directly, here is list of available methods for all built-in providers:
$user->getId();
$user->getNickname();
$user->getName();
$user->getEmail();
$user->getAvatar();
So your code should be:
private function findOrCreateUser($facebookUser)
{
// When I dd($facebookuser) it gives json stated above
$authUser = User::where('facebook_id', $facebookUser->id)->first();
if ($authUser){
return $authUser;
}
return User::create([
'name' => $facebookUser->getName(),
'email' => $facebookUser->getEmail(),
'facebook_id' => $facebookUser->getId(),
'avatar' => $facebookUser->getAvatar(),
'facebookAccount' => 1
]);
}
Don't forget to state those columns above in $fillable property of User model:
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'name', 'email', 'password', 'facebook_id', 'avatar', 'facebookAccount'
];
Otherwise fill the attributes manually:
$user = new User;
$user->name = $facebookUser->getName();
$user->email = $facebookUser-> getEmail();
$user->facebook_id = $facebookUser->getId();
$user->facebookAccount = 1;
$user->save();
return $user;