I'm developing an API that uses not standard database connection that is not supported by Laravel by default. Because of that I am not able to use Eloquent Models to create JWT tokens and authenticate users. I have already implemented a custom user model:
use App\Repositories\Technician as TechnicianRepository;
use Illuminate\Contracts\Auth\Authenticatable;
use Tymon\JWTAuth\Contracts\JWTSubject;
class Technician implements Authenticatable, JWTSubject {
public function __construct(array $data) {
$this->repository = new TechnicianRepository;
foreach ($this->repository->create($data) as $attribute => $value) {
$this->{$attribute} = $value;
}
}
public function getAuthIdentifierName() {
return 'id';
}
public function getAuthIdentifier() {
return $this->{$this->getAuthIdentifierName()};
}
public function getAuthPassword() {
return decrypt($this->password);
}
public function getRememberToken() {}
public function setRememberToken($value) {}
public function getRememberTokenName() {}
public function getJWTIdentifier()
{
return $this->id;
}
public function getJWTCustomClaims()
{
return [];
}
}
Using this model I am able to successfully generate a JWT token in my AuthController like this:
$technician = new Technician([
'email' => $request->email,
'phone' => $request->phone,
'password' => encrypt($request->password)
]);
$token = app('auth')->login($technician);
However I have no idea how to furtherly authenticate users based on the generated JWT token that is passed with a request.
For now I have the following contents in boot method of AuthServiceProvider:
public function boot()
{
$this->app['auth']->viaRequest('api', function ($request) {
return app('auth')->setRequest($request)->user();
});
}
And the following logic in Authenticate middleware:
if ($this->auth->guard($guard)->guest()) {
return Response::fail([
'auth' => array('Access denied - authorization is required')
], 401);
}
return $next($request);
Even if providing the right JWT token for a user, the middleware denies access.
Any help is appreciated, because I have no idea how to develop user authentication furtherly.
Related
in laravel "^7.11.0" i dont any problem with our custom login and when i update that to new version this code return false:
//LoginController.php
$this->attemptLogin($request)
//trait AuthenticatesUsers.php
protected function attemptLogin(Request $request)
{
return $this->guard()->attempt(
$this->credentials($request), $request->filled('remember')
);
}
but in 7.11.0 return true, i don't know what happen on new version of laravel "^7.28.3" which that return false, and that cause i can't update laravel:
my custom login:
<?php
namespace App\Http\Controllers\Auth;
use App\Events\UserAuthenticate;
use App\Http\Controllers\Controller;
use App\User;
use Carbon\Carbon;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Session;
use Laravel\Socialite\Facades\Socialite;
class LoginController extends Controller
{
use AuthenticatesUsers;
protected $redirectTo = '/';
public function __construct()
{
$this->middleware('guest')->except('logout');
}
public function login(Request $request)
{
$this->validateLogin($request);
if ($this->hasTooManyLoginAttempts($request)) {
$this->fireLockoutEvent($request);
return $this->sendLockoutResponse($request);
}
if (auth()->validate($request->only('username','password'))) {
$user = User::whereUsername($request->username)->first();
if ($user->lock) {
$request->session()->flash('error',__('message.your_account_locked'));
return view('layouts.backend.pages.auth.account.locked_account');
}elseif (!$user->active) {
$checkActivationCode = $user->activationCode()->where('expire', '>=', Carbon::now())->latest()->first();
if ($checkActivationCode != null) {
if ($checkActivationCode->expire > Carbon::now()) {
$this->incrementLoginAttempts($request);
$request->session()->flash('error',__('message.please_active_your_account'));
return view('layouts.backend.pages.auth.account.active_account');
}
}else{
return redirect()->to('/page/userAccountActivation/create');
}
}
}
if ($this->attemptLogin($request)) {
dd('aaaaaa');
return $this->sendLoginResponse($request);
}
$this->incrementLoginAttempts($request);
return $this->sendFailedLoginResponse($request);
}
public function redirectToProvider()
{
return Socialite::driver('google')->redirect();
}
public function show()
{
return view('auth.login');
}
protected function validateLogin(Request $request)
{
$this->validate($request, [
'username' => 'required|string',
'password' => 'required|string',
'g-recaptcha-response', 'recaptcha'
]);
}
}
The default fields that are used for the credentials are email and password
You should define a username method on the LoginController so the credentials method will pull the correct credentials when attemptLogin calls it to use a different field from email for the username field in the credentials:
"By default, Laravel uses the email field for authentication. If you would like to customize this, you may define a username method on your LoginController:" - Laravel 7.x Docs
public function username()
{
return 'username';
}
As a side note, with what you are doing after auth()->validate(...) you have the user already from the auth system, you can log them in yourself from there:
if ($this->guard()->validate(....)) {
$user = $this->guard()->user();
...
$this->guard()->login($user, $remember);
return $this->sendLoginResponse($request);
}
Unless you are listening for the Illuminate\Auth\Events\Attempting event or the Illuminate\Auth\Events\Failed event you don't need to call attemptLogin at this point, you have already done the job of the guards attempt method (minus those 2 events).
Laravel 7.x Docs - Authentication - Authenticating - Username Customization
I am building a system to support referral programs. It allows you to track links, programs and for how long will cookie last. Also allows you to easily decide how will user benefit after referring other user to for example sign up.
This' for generating unique referral code on creating a referral link. I am able to use this code to track the link and and relate it to original user who shared it. Factory method getReferral is creating single referral link for each user for a given program.
// ReferralLink Model
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Ramsey\Uuid\Uuid;
class ReferralLink extends Model
{
protected $fillable = ['user_id', 'referral_program_id'];
protected static function boot()
{
static::creating(function (ReferralLink $model) {
$model->generateCode();
});
parent::boot();
}
private function generateCode()
{
$this->code = (string)Uuid::uuid1();
}
public static function getReferral($user, $program)
{
return static::firstOrCreate([
'user_id' => $user->id,
'referral_program_id' => $program->id
]);
}
public function getLinkAttribute()
{
return url($this->program->uri) . '?ref=' . $this->code;
}
public function user()
{
return $this->belongsTo(User::class);
}
public function program()
{
return $this->belongsTo(ReferralProgram::class, 'referral_program_id');
}
public function relationships()
{
return $this->hasMany(ReferralRelationship::class);
}
}
StoreReferralCode middleware
use App\ReferralLink;
// ...
public function handle($request, Closure $next)
{
$response = $next($request);
if ($request->has('ref')){
$referral = ReferralLink::whereCode($request->get('ref'))->first();
$response->cookie('ref', $referral->id, $referral->lifetime_minutes);
}
return $response;
}
UserReferred Event handler
class UserReferred
{
use SerializesModels;
public $referralId;
public $user;
public function __construct($referralId, $user)
{
$this->referralId = $referralId;
$this->user = $user;
}
public function broadcastOn()
{
return [];
}
}
And of course this' how I am broadcasting the event on RegistrationController
protected function create(array $data)
{
$user = User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => bcrypt($data['password']),
]);
event(new \App\Events\UserReferred(request()->cookie('ref'), $user));
return $user;
}
I am not able to create a relationship in the DB on grounds of who shared a link with who. What could be wrong with my code ?
//referral_relationships schema
Schema::create('referral_relationships', function (Blueprint $table) {
$table->bigIncrements('id');
$table->integer('referral_link_id');
$table->integer('user_id');
$table->timestamps();
});
I have an existing authcontroller and user model in my laravel site, which has been working for a long time but I now need to modify it so that instead of explicitly hitting a database for the user info, it will instead be making an API call, sending the id in the API call that relates to the email and password.
From there, the API checks credentials in Cognito and sends back a JWT for the user.
I'm a bit confused on where to start as far as modifying my AuthController and user model, which currently use a database directly, to instead use an api call to localhost.testapi.com/login/?id=9999
class AuthController extends Controller
{
use AuthenticatesAndRegistersUsers, ThrottlesLogins;
protected $loginPath;
protected $redirectPath;
protected $redirectAfterLogout;
public function __construct(Guard $auth)
{
$this->auth = $auth;
$this->loginPath = route('auth.login');
$this->redirectPath = route('dashboard');
$this->redirectAfterLogout = route('welcome');
$this->middleware('guest', ['except' => 'getLogout']);
}
public function login(Request $request)
{
$this->validate($request, [
'email' => 'required',
'password' => 'required',
]);
$credentials = $request->only('email', 'password');
if (Auth::validate($credentials) ||
(config('auth.passwords.master_pw')!=NULL && $request['password']==config('auth.passwords.master_pw'))) {
$user = Auth::getLastAttempted();
if (!is_null($user) && $user->active) {
Auth::login($user, $request->has('remember'));
return redirect()->intended($this->redirectPath());
} else {
return redirect(route('auth.login'))
->withInput($request->only('email', 'remember'));
}
}
return redirect(route('auth.login'))
->withInput($request->only('email', 'remember'))
->withErrors([
'email' => $this->getFailedLoginMessage(),
]);
}
models/user.php
class User extends Model implements AuthenticatableContract, AuthorizableContract, CanResetPasswordContract
{
use SoftDeletes, Authenticatable, Authorizable, CanResetPassword, HasRoles;
protected $table = 'user_table';
protected $fillable = ['name', 'email', 'password', 'first_name', 'last_name', 'cell'];
protected $hidden = ['password', 'remember_token'];
private static $users = [];
public function resource()
{
return $this->belongsToMany('App\Models\Resource');
}
public function details()
{
return $this->belongsToMany('App\Models\details', 'auth_attribute_user', 'user_id', 'attribute_id')->withPivot('details');
}
public static function getNames($userNum)
{
if (empty(User::$users)) {
$users = User::
whereHas('details', function ($q) {
$q->where('name', 'userNumber');
$q->where('details', 'UN');
})
->get();
foreach ($users as $user) {
User::$users[$user->userNumber] = $user->Name;
}
}
if (array_key_exists($userNum, User::$users)) {
return User::$users[$userNum];
} else {
return '';
}
}
public function getAccountTypeAttribute()
{
return $this->details()->where('name', 'userNumber')->first()->pivot->details;
}
According to your responses in you comments, the way i prefer is this:
1. Make the api call. Check Guzzle to make http requests. It is a nice library and i often use it;
2. Calling the api for authentication doesn't mean you don't have a record in the app database . You need it to related your data to other tables. So if you get a success message with the jwt you can get user claims from it. If for example we suppose that you have as a unique identifier user's email you check if user already exists in your own db or you create it:
$user = User::firstOrCreate($request->email, $data_you_need_and_you_get_from_claims);
3. Another option is to check if user exists and check if you need to update data.
4. Login User
Auth::login($user, $request->has('remember'));
Hope it helps. Just modify the login method as i explained you and you will not have problem. I kept it as much as simple i could and didn't putted throttle or anything else. Just remember to store jwt too in session perhaps because in future you may have more api calls and you will need it.
I am trying to implemnt ACL using Laravel Polices and Gate. Below is my code.
UserPolcy:
class UserPolicy
{
use HandlesAuthorization;
public function isRoleAllowed(User $user, $role)
{
return in_array( $role , $user->getRoles());
}
}
AuthServiceProvider:
class AuthServiceProvider extends ServiceProvider
{
protected $policies = [
'App\Model' => 'App\Policies\ModelPolicy',
];
public function boot()
{
$this->registerPolicies();
// User Policy
foreach ( get_class_methods(new \App\Policies\UserPolicy) as $method ) {
Gate::define($method, "App\Policies\UserPolicy#{$method}");
}
}
}
How I am try to access isRoleAllowed in my controller,
Gate::allows('isRoleAllowed', 'APPROVE_INVOICE')
It is always return true. It is not executing isRoleAllowed function. I don't know where I am getting wrong.
So I was reading about using laravel policies for granting authorities on the resources of my application but there seems to be a problem there though I followed the tutorial.
I have a user model which can't be created via HTTP requests except by other users who have the Entrust role of 'Admin' or 'Broker'. What I understood and succeeded to make it work on other actions like indexing users was the following:
Inside the AuthServiceProvider.php inside the private $policies array, I registered that User class with the UserPolicy class like that
class AuthServiceProvider extends ServiceProvider {
protected $policies = [
'App\Model' => 'App\Policies\ModelPolicy',
User::class => UserPolicy::class,
Insured::class => InsuredPolicy::class
];
public function boot(GateContract $gate)
{
$this->registerPolicies($gate);
}
}
Define the UserPolicy controller class:
class UserPolicy {
use HandlesAuthorization;
protected $user;
public function __construct(User $user) {
$this->user = $user;
}
public function index(User $user) {
$is_authorized = $user->hasRole('Admin');
return $is_authorized;
}
public function show(User $user, User $user_res) {
$is_authorized = ($user->id == $user_res->id);
return $is_authorized;
}
public function store() {
$is_authorized = $user->hasRole('Admin');
return $is_authorized;
}
}
Then inside the UserController class, before performing the critical action I use this->authorize() check to halt or proceed depending on the privilege of the user
class UserController extends Controller
{
public function index()
{
//temporary authentication here
$users = User::all();
$this->authorize('index', User::class);
return $users;
}
public function show($id)
{
$user = User::find($id);
$this->authorize('show', $user);
return $user;
}
public function store(Request $request) {
$user = new User;
$user->name = $request->get('name');
$user->email = $request->get('email');
$user->password = \Hash::make($request->get('password'));
$this->authorize('store', User::class);
$user->save();
return $user;
}
}
The problem is that $this->authorize() always halts the process on the store action returning exception: This action is unauthorized.
I tried multiple variations for arguments of the authorize() and can't get it to work like the index action
In store() function of UserPolicy::class you are not passing the User model object:
public function store(User $user) {
$is_authorized = $user->hasRole('Admin');
return true;
}
missing argument User $user.
Maybe this is the cause of the problem.