Authentication with 2 different tables - authentication

I need to create a new "auth" config with another table and users. I have one table for the "admin" users and another table for the normal users.
But how can I create another instance of Auth with a different configuration?

While trying to solve this problem myself, I found a much simpler way. I basically created a custom ServiceProvider to replace the default Auth one, which serves as a factory class for Auth, and allows you to have multiple instances for multiple login types. I also stuck it all in a package which can be found here: https://github.com/ollieread/multiauth
It's pretty easy to use really, just replace the AuthServiceProvider in app/config/app.php with Ollieread\Multiauth\MultiauthServiceProvider, then change app/config/auth.php to look something like this:
return array(
'multi' => array(
'account' => array(
'driver' => 'eloquent',
'model' => 'Account'
),
'user' => array(
'driver' => 'database',
'table' => 'users'
)
),
'reminder' => array(
'email' => 'emails.auth.reminder',
'table' => 'password_reminders',
'expire' => 60,
),
);
Now you can just use Auth the same way as before, but with one slight difference:
Auth::account()->attempt(array(
'email' => $attributes['email'],
'password' => $attributes['password'],
));
Auth::user()->attempt(array(
'email' => $attributes['email'],
'password' => $attributes['password'],
));
Auth::account()->check();
Auth::user()->check();
It also allows you to be logged in as multiple user types simultaneously which was a requirement for a project I was working on. Hope it helps someone other than me.
UPDATE - 27/02/2014
For those of you that are just coming across this answer, I've just recently added support for reminders, which can be accessed in the same factory style way.

You can "emulate" a new Auth class.
Laravel Auth component is basically the Illuminate\Auth\Guard class, and this class have some dependencies.
So, basically you have to create a new Guard class and some facades...
<?php
use Illuminate\Auth\Guard as AuthGuard;
class CilentGuard extends AuthGuard
{
public function getName()
{
return 'login_' . md5('ClientAuth');
}
public function getRecallerName()
{
return 'remember_' . md5('ClientAuth');
}
}
... add a ServiceProvider to initialize this class, passing it's dependencies.
<?php
use Illuminate\Support\ServiceProvider;
use Illuminate\Auth\EloquentUserProvider;
use Illuminate\Hashing\BcryptHasher;
use Illuminate\Auth\Reminders\PasswordBroker;
use Illuminate\Auth\Reminders\DatabaseReminderRepository;
use ClientGuard;
use ClientAuth;
class ClientServiceProvider extends ServiceProvider
{
public function register()
{
$this->registerAuth();
$this->registerReminders();
}
protected function registerAuth()
{
$this->registerClientCrypt();
$this->registerClientProvider();
$this->registerClientGuard();
}
protected function registerClientCrypt()
{
$this->app['client.auth.crypt'] = $this->app->share(function($app)
{
return new BcryptHasher;
});
}
protected function registerClientProvider()
{
$this->app['client.auth.provider'] = $this->app->share(function($app)
{
return new EloquentUserProvider(
$app['client.auth.crypt'],
'Client'
);
});
}
protected function registerClientGuard()
{
$this->app['client.auth'] = $this->app->share(function($app)
{
$guard = new Guard(
$app['client.auth.provider'],
$app['session.store']
);
$guard->setCookieJar($app['cookie']);
return $guard;
});
}
protected function registerReminders()
{
# DatabaseReminderRepository
$this->registerReminderDatabaseRepository();
# PasswordBroker
$this->app['client.reminder'] = $this->app->share(function($app)
{
return new PasswordBroker(
$app['client.reminder.repository'],
$app['client.auth.provider'],
$app['redirect'],
$app['mailer'],
'emails.client.reminder' // email template for the reminder
);
});
}
protected function registerReminderDatabaseRepository()
{
$this->app['client.reminder.repository'] = $this->app->share(function($app)
{
$connection = $app['db']->connection();
$table = 'client_reminders';
$key = $app['config']['app.key'];
return new DatabaseReminderRepository($connection, $table, $key);
});
}
public function provides()
{
return array(
'client.auth',
'client.auth.provider',
'client.auth.crypt',
'client.reminder.repository',
'client.reminder',
);
}
}
In this Service Provider, I put some example of how to create a 'new' password reminder component to.
Now you need to create two new facades, one for authentication and one for password reminders.
<?php
use Illuminate\Support\Facades\Facade;
class ClientAuth extends Facade
{
protected static function getFacadeAccessor()
{
return 'client.auth';
}
}
and...
<?php
use Illuminate\Support\Facades\Facade;
class ClientPassword extends Facade
{
protected static function getFacadeAccessor()
{
return 'client.reminder';
}
}
Of course, for password reminders, you need to create the table in database, in order to work. In this example, the table name should be client_reminders, as you can see in the registerReminderDatabaseRepository method in the Service Provider. The table structure is the same as the original reminders table.
After that, you can use your ClientAuth the same way you use the Auth class. And the same thing for ClientPassword with the Password class.
ClientAuth::gust();
ClientAuth::attempt(array('email' => $email, 'password' => $password));
ClientPassword::remind($credentials);
Don't forget to add your service provider to the service providers list in the app/config/app.php file.
UPDATE:
If you are using Laravel 4.1, the PasswordBroker doesn't need the Redirect class anymore.
return new PasswordBroker(
$app['client.reminder.repository'],
$app['client.auth.provider'],
$app['mailer'],
'emails.client.reminder' // email template for the reminder
);
UPDATE 2
Laravel 5.2 just introduced multi auth, so this is no longer needed in this version.

Ok, I had the same problem and here is how I solved it:
actually in laravel 4 you can simply change the auth configs at runtime so to do the trick you can simply do the following in your App::before filter:
if ($request->is('admin*'))
{
Config::set('auth.model', 'Admin');
}
this will make the Auth component to use th Admin model when in admin urls. but this will lead to a new problem, because the login session key is the same if you have two users in your admins and users table with the same id you will be able to login to the admin site if you have logged in before as a regular user! so to make the two different authetications completely independent I did this trick:
class AdminGuard extends Guard
{
public function getName()
{
return 'admin_login_'.md5(get_class($this));
}
public function getRecallerName()
{
return 'admin_remember_'.md5(get_class($this));
}
}
Auth::extend('eloquent.admin', function()
{
return new AdminGuard(new EloquentUserProvider(new BcryptHasher, 'Admin'), App::make('session.store'));
});
and change the App::before code to:
if ($request->is('admin*'))
{
Config::set('auth.driver', 'eloquent.admin');
Config::set('auth.model', 'Admin');
}
you can see that I made a new auth driver and rewrote some methods on the Guard class so it will generate different session keys for admin site. then I changed the driver for the admin site. good luck.

I had the same problem yesterday, and I ended up creating a much simpler solution.
My requirements where 2 different tables in two different databases. One table was for admins, the other was for normal users. Also, each table had its own way of hashing. I ended up with the following (Code also available as a gist on Github: https://gist.github.com/Xethron/6790029)
Create a new UserProvider. I called mine MultiUserProvider.php
<?php
// app/libraries/MultiUserProvider.php
use Illuminate\Auth\UserProviderInterface,
Illuminate\Auth\UserInterface,
Illuminate\Auth\GenericUser;
class MultiUserProvider implements UserProviderInterface {
protected $providers;
public function __construct() {
// This should be moved to the config later...
// This is a list of providers that can be used, including
// their user model, hasher class, and hasher options...
$this->providers = array(
'joomla' => array(
'model' => 'JoomlaUser',
'hasher' => 'JoomlaHasher',
)
'another' => array(
'model' => 'AnotherUser',
'hasher' => 'AnotherHasher',
'options' => array(
'username' => 'empolyee_number',
'salt' => 'salt',
)
),
);
}
/**
* Retrieve a user by their unique identifier.
*
* #param mixed $identifier
* #return \Illuminate\Auth\UserInterface|null
*/
public function retrieveById($identifier)
{
// Returns the current provider from the session.
// Should throw an error if there is none...
$provider = Session::get('user.provider');
$user = $this->createModel($this->providers[$provider]['model'])->newQuery()->find($identifier);
if ($user){
$user->provider = $provider;
}
return $user;
}
/**
* Retrieve a user by the given credentials.
*
* #param array $credentials
* #return \Illuminate\Auth\UserInterface|null
*/
public function retrieveByCredentials(array $credentials)
{
// First we will add each credential element to the query as a where clause.
// Then we can execute the query and, if we found a user, return it in a
// Eloquent User "model" that will be utilized by the Guard instances.
// Retrieve the provider from the $credentials array.
// Should throw an error if there is none...
$provider = $credentials['provider'];
$query = $this->createModel($this->providers[$provider]['model'])->newQuery();
foreach ($credentials as $key => $value)
{
if ( ! str_contains($key, 'password') && ! str_contains($key, 'provider'))
$query->where($key, $value);
}
$user = $query->first();
if ($user){
Session::put('user.provider', $provider);
$user->provider = $provider;
}
return $user;
}
/**
* Validate a user against the given credentials.
*
* #param \Illuminate\Auth\UserInterface $user
* #param array $credentials
* #return bool
*/
public function validateCredentials(UserInterface $user, array $credentials)
{
$plain = $credentials['password'];
// Retrieve the provider from the $credentials array.
// Should throw an error if there is none...
$provider = $credentials['provider'];
$options = array();
if (isset($this->providers[$provider]['options'])){
foreach ($this->providers[$provider]['options'] as $key => $value) {
$options[$key] = $user->$value;
}
}
return $this->createModel($this->providers[$provider]['hasher'])
->check($plain, $user->getAuthPassword(), $options);
}
/**
* Create a new instance of a class.
*
* #param string $name Name of the class
* #return Class
*/
public function createModel($name)
{
$class = '\\'.ltrim($name, '\\');
return new $class;
}
}
Then, I told Laravel about my UserProvider by adding the following lines to the top of my app/start/global.php file.
// app/start/global.php
// Add the following few lines to your global.php file
Auth::extend('multi', function($app) {
$provider = new \MultiUserProvider();
return new \Illuminate\Auth\Guard($provider, $app['session']);
});
And then, I told Laravel to use my user provider instead of EloquentUserProvider in app/config/auth.php
'driver' => 'multi',
Now, when I authenticate, I do it like so:
Auth::attempt(array(
'email' => $email,
'password' => $password,
'provider'=>'joomla'
)
)
The class would then use the joomlaUser model, with the joomlaHasher, and no options for the hasher... If using 'another' provider, it will include options for the hasher.
This class was built for what I required but can easily be changed to suite your needs.
PS: Make sure the autoloader can find MultiUserProvider, else it won't work.

I'm using Laravel 5 native auth to handle multiple user tables...
It's not difficult, please check this Gist:
https://gist.github.com/danielcoimbra/64b779b4d9e522bc3373
UPDATE: For Laravel 5, if you need a more robust solution, try this package:
https://github.com/sboo/multiauth
Daniel

Related

Laravel8 role based access using and pivot tables and middlewares

I am trying to write a large project using laravel8 and will need role based access. So I have created a roles table and linked it to the users table via a modal table called role_user. Now I am able to create the access and perform checks inside controllers and users model and everything works correctly
my problem is that this way I have to keep checcking if a user has access in each and every function for all my user access levels and this is tedious.
I have thus tried to convert this approcah and use middlewares but the problem is that I am unable to redirect the users to the appropriate dashboard upon authentication and they are both being redirected to the home page that is designated for users only.
I have tried the following
I have addes the following code to the users model to create a many to many relationship between the users and the roles and also to check if the user has roles and peform appropriate tasks as per the code blocks
public function roles() {
return $this->belongsToMany(Role::class);
}
public function checkRoles($roles) {
if ( ! is_array($roles)) {
$roles = [$roles];
return false;
}
if ( ! $this->hasAnyRole($roles)) {
auth()->logout();
abort(404);
}
}
public function hasAnyRole($roles): bool {
return (bool) $this->roles()->whereIn('name', $roles)->first();
}
public function hasRole($role): bool {
return (bool) $this->roles()->where('name', $role)->first();
}
Now this would have worked if I proceeded to peforem checks on each controller's model directly but after creating middlewares for each role and peforming checks in each as follows things failled
a) Admin Middleware
public function handle(Request $request, Closure $next){
if (Auth::user() && $request->user()->hasRole('admin')) {
return $next($request);
}
return redirect('home')->with('error','You have not admin access');
}
b) SuperAdmin Middleware
public function handle(Request $request, Closure $next) {
if (Auth::user() && $request->user()->hasRole('super_admin')) {
return $next($request);
}
return redirect('home')->with('error','You have not permission to peform this task');
}
Now after registering all the middlewareds inside the Kernel class, I modified the login Controller class and added the following code inside the construct function:
if(Auth::check()){
if(Auth::user()->hasRole('super_admin')){
return redirect(RouteServiceProvider::SUPERADMINADMINHOME);
}elseif(Auth::user()->hasRole('admin')){
return redirect(RouteServiceProvider::ADMINHOME);
}elseif(Auth::user()->hasRole('vendor')){
return redirect(RouteServiceProvider::VENDORHOME);
}else {
return redirect(RouteServiceProvider::HOME);
}
}
I also updated my routes as follows
Route::group(['prefix' => 'super', 'as' => 'super.', 'namespace' => 'Super', 'middleware' => ['Super']], function () {
Route::get('/', [App\Http\Controllers\SuperAdminController::class,'index'])->name('superadminhome');
});
Route::group(['prefix' => 'admin', 'as' => 'admin.', 'namespace' => 'Admin', 'middleware' => ['Admin']], function () {
Route::get('/', [App\Http\Controllers\AdminController::class,'index'])->name('adminhome');
});
Route::group(['prefix' => 'vendor', 'as' => 'vendor.', 'namespace' => 'Vendor', 'middleware' => ['Vendor']], function () {
Route::get('/', [App\Http\Controllers\VendorController::class,'index'])->name('vendorhome');
});
After doing all this, I tried tom login with the superadmin credentials and the admin credentials but all of them take me to the same page home. What could I be doing wrong or where can I get step by step guide to how to achieve this task noting that I am new to middlewares in laravel.
From what I can see you are having this problem becasue the checks are all yielding false hence the last is being taken into account. That would mean that the way in which you are checking for the roles is probably wrong. I would suggest looping through the roles and using a switch statement. checking if it matches the ones you need and then redirecting appropriately.
This would mean changing your if statements that check for the availability of a specific roles.
That is this part of your code
if(Auth::user()->hasRole('super_admin')){
return redirect(RouteServiceProvider::SUPERADMINADMINHOME);
}elseif(Auth::user()->hasRole('admin')){
return redirect(RouteServiceProvider::ADMINHOME);
}elseif(Auth::user()->hasRole('vendor')){
return redirect(RouteServiceProvider::VENDORHOME);
}else {
return redirect(RouteServiceProvider::HOME);
}

Podio API - Session Management class error in accessing tokens in Redis

I'm trying to use Session Management for API calls, so I don't trigger Auth class function everytime my script run. I mostly used App ID authentication so I used the sample provided for Redis.
However, I'm getting an error "Fatal error: Uncaught Error: Cannot access self:: when no class scope is active in /var/www/html/authcheck.php:22 Stack trace: #0 {main} thrown in /var/www/html/authcheck.php on line 22"
The code in line 22 is this - Podio::$oauth = self::$session_manager->get(Podio::$auth_type);
Here's the PHP Script for Session manager class:
Filename: SessionManager.php
<?php
require ('podio/podio_lib/PodioAPI.php');
require ('predis/autoload.php');
class PodioRedisSession {
/**
* Create a pointer to Redis when constructing a new object
*/
public function __construct() {
$this->redis = new Predis\Client();
}
/**
* Get oauth object from session, if present. We use $auth_type as
* basis for the cache key.
*/
public function get($auth_type = null) {
// If no $auth_type is set, just return empty
// since we won't be able to find anything.
if (!$auth_type) {
return new PodioOauth();
}
$cache_key = "podio_cache_".$auth_type['type']."_".$auth_type['identifier'];
// Check if we have a stored session
if ($this->redis->exists($cache_key)) {
// We have a session, create new PodioOauth object and return it
$cached_value = $this->redis->hgetall($cache_key);
return new PodioOAuth(
$cached_value['access_token'],
$cached_value['refresh_token'],
$cached_value['expires_in'],
array("type"=>$cached_value['ref_type'], "id"=>$cached_value['ref_id'])
);
}
// Else return an empty object
return new PodioOAuth();
}
/**
* Store the oauth object in the session. We ignore $auth_type since
* it doesn't work with server-side authentication.
*/
public function set($oauth, $auth_type = null) {
$cache_key = "podio_cache_".$auth_type['type']."_".$auth_type['identifier'];
// Save all properties of the oauth object in redis
$this->redis->hmset = array(
'access_token' => $oauth->access_token,
'refresh_token' => $oauth->refresh_token,
'expires_in' => $oauth->expires_in,
'ref_type' => $oauth->ref["type"],
'ref_id' => $oauth->ref["id"],
);
}
}
?>
Filename: authcheck.php
<?php
require ('podio/podio_lib/PodioAPI.php');
include ('SessionManager.php');
$client_id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
$client_secret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxx";
$app_id = "xxxxxxxxxxx";
$app_token = "xxxxxxxxxxxxxxxxxxx";
Podio::setup($client_id, $client_secret, array(
"session_manager" => "PodioRedisSession"
));
// podio-php will attempt to find a session automatically, but will fail
because
// it doesn't know which $auth_type to use.
// So we must attempt to locate a session manually.
Podio::$auth_type = array(
"type" => "app",
"identifier" => $app_id
);
Podio::$oauth = self::$session_manager->get(Podio::$auth_type);
// Now we can check if anything could be found in the cache and
// authenticate if it couldn't
if (!Podio::is_authenticated()) {
// No authentication found in session manager.
// You must re-authenticate here.
Podio::authenticate_with_app($app_id, $app_token);
} else {
//echo "<pre>".print_r($_SESSION, true)."</pre>";
echo "You already authenticated!";
}
// // We can safely switch to another app now
// // First attempt to get authentication from cache
// // If that fails re-authenticate
// Podio::$auth_type = array(
// "type" => "app",
// "identifier" => $another_app_id
// );
// Podio::$oauth = self::$session_manager->get(Podio::$auth_type);
// if (!Podio::is_authenticated()) {
// // No authentication found in session manager.
// // You must re-authenticate here.
// Podio::authenticate_with_app($another_app_id, $another_app_token);
// }
?>
Hi opted not to use redis instead I used session and PDO Mysql on storing podio auth.

Is my order complete return approach correct?

When a customer is returned to the following URL (example);
http://prestashop.dev/index.php?action=completed&controller=callback&fc=module&hmac={valid-hmac}&merchant_order_id=14&module=chippin
After a successful payment, It will call on this FrontController sub-class;
class ChippinCallbackModuleFrontController extends ModuleFrontController
{
public function postProcess()
{
$chippin = new Chippin();
$payment_response = new PaymentResponse();
$payment_response->getPostData();
// if a valid response from gateway
if(ChippinValidator::isValidHmac($payment_response)) {
// "action" is passed as a param in the URL. don't worry, the Hmac can tell if it's valid or not.
if ($payment_response->getAction() === "completed") {
// payment_response->getMerchantOrderId() will just return the id_order from the orders table
$order_id = Order::getOrderByCartId((int) ($payment_response->getMerchantOrderId()));
$order = new Order($order_id);
// this will update the order status for the benefit of the merchant.
$order->setCurrentState(Configuration::get('CP_OS_PAYMENT_COMPLETED'));
// assign variables to smarty (copied this from another gateway, don't really understand smarty)
$this->context->smarty->assign(
array(
'order' => $order->reference,
)
);
// display this template
$this->setTemplate('confirmation.tpl');
I'm quite new to Prestashop. I'm just not sure if this is technically done or not. The confirmation.tlp view does display with the order->reference and the order status is updated to "Completed" but is this all I need?
Are there any other considerations? I have the opportunity to call a hookDisplayPaymentReturn at this point but why should I?
I seem to have a pretty standard return page. Is this enough;
Update - Do I just call a hook something like;
public function displayPaymentReturn()
{
$params = $this->displayHook();
if ($params && is_array($params)) {
return Hook::exec('displayPaymentReturn', $params, (int) $this->module->id);
}
return false;
}
As far as I can see everything seems okay for me.
You should consider adding hookDisplayPaymentReturn it allows other modules to add code to your confirmation page. For example a Google module could add javascript code that sends order informations to analytics on confirmation page.
EDIT
class ChippinCallbackModuleFrontController extends ModuleFrontController
{
public function postProcess()
{
$chippin = new Chippin();
$payment_response = new PaymentResponse();
$payment_response->getPostData();
// if a valid response from gateway
if(ChippinValidator::isValidHmac($payment_response)) {
// "action" is passed as a param in the URL. don't worry, the Hmac can tell if it's valid or not.
if ($payment_response->getAction() === "completed") {
// payment_response->getMerchantOrderId() will just return the id_order from the orders table
$order_id = Order::getOrderByCartId((int) ($payment_response->getMerchantOrderId()));
$order = new Order($order_id);
// this will update the order status for the benefit of the merchant.
$order->setCurrentState(Configuration::get('CP_OS_PAYMENT_COMPLETED'));
// assign variables to smarty (copied this from another gateway, don't really understand smarty)
$this->context->smarty->assign(
array(
'order' => $order->reference,
'hookDisplayPaymentReturn' => Hook::exec('displayPaymentReturn', $params, (int) $this->module->id);
)
);
$cart = $this->context->cart;
$customer = new Customer($cart->id_customer);
Tools::redirect('index.php?controller=order-confirmation&id_cart='.$cart->id.'&id_module='.$this->module->id.'&id_order='.$order->id.'&key='.$customer->secure_key);
And in your module :
class myPaymentModule extends PaymentModule
{
public function install()
{
if (!parent::install() || !$this->registerHook('paymentReturn'))
return false;
return true;
}
// Example taken from bankwire module
public function hookPaymentReturn($params)
{
$state = $params['objOrder']->getCurrentState();
$this->smarty->assign(array(
'total_to_pay' => Tools::displayPrice($params['total_to_pay'], $params['currencyObj'], false),
'bankwireDetails' => Tools::nl2br($this->details),
'bankwireAddress' => Tools::nl2br($this->address),
'bankwireOwner' => $this->owner,
'status' => 'ok',
'id_order' => $params['objOrder']->id
));
if (isset($params['objOrder']->reference) && !empty($params['objOrder']->reference))
$this->smarty->assign('reference', $params['objOrder']->reference);
return $this->display(__FILE__, 'confirmation.tpl');
}
}

Laravel 5 customizing authentication

In Laravel 5 how can you customize the default authentication which comes out of the box? For example I have multiple types of users to authenticate against. Each type of user is defined by a role i.e Jobseeker, Recruiter etc. Each type of user will have a different type of registration form to capture some of the profile details as well. So I have the following tables:
users
roles
role_user
jobseeker_profile
recruiter_profile
The default authcontroller and passwordcontroller in Laravel 5 uses traits for all the authentication methods. How would you go about customizing it - do you guys edit the existing trait files? So for example the getRegister method returns the register view but I would want it to check the route before deciding which view to show.
// default method
public function getRegister()
{
return view('auth.register');
}
// custom method
public function getRegister()
{
if (Request::is('jobseeker/register'))
{
return view('auth.jobseeker_register');
}
elseif (Request::is('recruiter/register'))
{
return view('auth.recruiter_register');
}
}
Similarly the default postLogin method is as follows:
public function postLogin(Request $request)
{
$this->validate($request, [
'email' => 'required|email', 'password' => 'required',
]);
$credentials = $request->only('email', 'password');
if ($this->auth->attempt($credentials, $request->has('remember')))
{
return redirect()->intended($this->redirectPath());
}
return redirect($this->loginPath())
->withInput($request->only('email', 'remember'))
->withErrors([
'email' => 'These credentials do not match our records.',
]);
}
But I would want the method to also check the user roles as follows:
public function postLogin(Request $request)
{
$this->validate($request, [
'email' => 'required|email', 'password' => 'required',
]);
$credentials = $request->only('email', 'password');
if ($this->auth->attempt($credentials, $request->has('remember')))
{
if(Auth::user()->role->name == 'recruiter')
{
return redirect()->to('/recruiter/dashboard');
}
elseif(Auth::user()->role->name == 'jobseeker')
{
return redirect()->to('jobseeker/dashboard');
}
}
return redirect($this->loginPath())
->withInput($request->only('email', 'remember'))
->withErrors([
'email' => 'These credentials do not match our records.',
]);
}
So my question is how do you go about customizing the existing authentication? Do you guys create a new controller perhaps CustomAuthController, CustomPasswordController and copy all the traits from the default auth controllers into these custom controllers and edit them as appropriate? I'm unable to find any Laravel 5 tutorials on how to acheive this - they all simply talk about the default out of the box authentication. If anyone has done something similar before I would love to hear about how you went about it and which files were edited to wire this custom auth up.
You have a couple of options:
Override the methods in the existing auth controller.
Just don’t implement the AuthenticatesAndRegistersUsers trait at all, and implement authentication logic entirely yourself.
With regards to redirect, I’d listen on the auth.login event, check your user’s type there, and then redirect to the specific dashboard there and then.

Yii 2 RESTful API authenticate with OAuth2 (Yii 2 advanced template)

REST API is working without authentication methods. Now i wanted to authenticate REST API with OAuth2 authentication for API requests via mobile application. I tried with yii2 guide, but it didn't work for me.
basically mobile user need to be login with username & password, if a username and password are correct, user need to be login and further API request need to be validate with token.
Do i need to create custom OAuth 2 client like this ?
Creating your own auth clients
access_token field in user table is empty. do i need to save it manually ?
how to return access_token as a respond?
is there any reason for user all three methods(HttpBasicAuth, HttpBearerAuth, QueryParamAuth) at once, why? how?
my application folder structure looks like below.
api
-config
-modules
--v1
---controllers
---models
-runtime
-tests
-web
backend
common
console
environments
frontend
api\modules\v1\Module.php
namespace api\modules\v1;
class Module extends \yii\base\Module
{
public $controllerNamespace = 'api\modules\v1\controllers';
public function init()
{
parent::init();
\Yii::$app->user->enableSession = false;
}
}
api\modules\v1\controllers\CountryController.php
namespace api\modules\v1\controllers;
use Yii;
use yii\rest\ActiveController;
use common\models\LoginForm;
use common\models\User;
use yii\filters\auth\CompositeAuth;
use yii\filters\auth\HttpBasicAuth;
use yii\filters\auth\HttpBearerAuth;
use yii\filters\auth\QueryParamAuth;
/**
* Country Controller API
*
* #author Budi Irawan <deerawan#gmail.com>
*/
class CountryController extends ActiveController
{
public $modelClass = 'api\modules\v1\models\Country';
public function behaviors()
{
$behaviors = parent::behaviors();
$behaviors['authenticator'] = [
//'class' => HttpBasicAuth::className(),
'class' => CompositeAuth::className(),
'authMethods' => [
HttpBasicAuth::className(),
HttpBearerAuth::className(),
QueryParamAuth::className(),
],
];
return $behaviors;
}
}
common\models\User.php
namespace common\models;
use Yii;
use yii\base\NotSupportedException;
use yii\behaviors\TimestampBehavior;
use yii\db\ActiveRecord;
use yii\web\IdentityInterface;
class User extends ActiveRecord implements IdentityInterface
{
const STATUS_DELETED = 0;
const STATUS_ACTIVE = 10;
public static function tableName()
{
return '{{%user}}';
}
public function behaviors()
{
return [
TimestampBehavior::className(),
];
}
public function rules()
{
return [
['status', 'default', 'value' => self::STATUS_ACTIVE],
['status', 'in', 'range' => [self::STATUS_ACTIVE, self::STATUS_DELETED]],
];
}
public static function findIdentity($id)
{
return static::findOne(['id' => $id, 'status' => self::STATUS_ACTIVE]);
}
public static function findIdentityByAccessToken($token, $type = null)
{
return static::findOne(['access_token' => $token]);
}
}
user table
id
username
auth_key
password_hash
password_reset_token
email
status
created_at
access_token
access_token was added after migrate user table
I'm using JWT for validating the request. Basically JWT is a token which also contain information about a user, and about the token itself such as the validity and the expiration time of the token. You can read more about JWT here.
The flow of my application is like this:
First, when a user logged in, create a JWT for the user
$key = base64_decode('some_random_string');
$tokenId = base64_encode(mcrypt_create_iv(32));
$issuedAt = time();
$notBefore = $issuedAt + 5;
$expire = $notBefore + 1800;
$user = User::findByEmail($email);
$data = [
'iss' => 'your-site.com',
'iat' => $issuedAt,
'jti' => $tokenId,
'nbf' => $notBefore,
'exp' => $expire,
'data' => [
'id' => $user->id,
'username' => $user->username,
//put everything you want (that not sensitive) in here
]
];
$jwt = JWT::encode($data, $key,'HS256');
return $jwt;
Then, the client (e.g the mobile app) must provide the token in every request via Authorization header. The header will look like this:
Authorization:Bearer [the JWT token without bracket]
In the User model, add a method like this for validating the token:
public static function findIdentityByAccessToken($token, $type = null) {
$key = base64_decode('the same key that used in login function');
try{
$decoded = JWT::decode($token, $key, array('HS256'));
return static::findByEmail($decoded->data->email);
}catch (\Exception $e){
return null;
}
}
The JWT library will raise an Exception if the token is no longer invalid (have been tampered or have been past the expiry time).
Then, add this to the behaviors function in every controller:
$behaviors['authenticator'] = [
'class' => HttpBearerAuth::className(),
'except' => ['login'] //action that you don't want to authenticate such as login
];
That's it! I hope this work like you wanted. Oh, and there is lot of JWT libraries that you can use (you can see it here), but I personally use this library by people from firebase
You Can create Your Auth System, usually I do it.
You can Save Tokens for every user, and after it authentify user by that token.
In every action You can send that token for authentify user.
You need to do the following:
set the token before saving the user in the User model.
add actionLogin in the UserController to return the auth_key on user login.
in each API request you send the auth_key in the header instead of
sending username and password.
to check if the auth_key is valid, define 'authenticator' in the
UserController behaviors.
you can find code samples in my answer to another question here