3
votes

I'm developing currently multi auth with Laravel Passport, so the app is gonna have users, and devices, and when i try to register with the devices it saves it to the devices database ,and if i try to login it gives me the Bearer token. But right now i want to get user middleware 'auth:api' or other way to get device information via token,but its seems that the tokens are stored in oauth_access_token table and with user_id .So is there a way to user laravel passport for another table except for users ? Thanks ?

Here is my code for Devices:

<?php
namespace App;
use Illuminate\Foundation\Auth\User as Authenticatable;

use Illuminate\Notifications\Notifiable;
use Laravel\Passport\HasApiTokens;
use SMartins\PassportMultiauth\HasMultiAuthApiTokens;

class Device extends Authenticatable{


    use Notifiable,HasApiTokens;

   protected $fillable = [
        'name', 'password'  ,
    ];
    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        'password', 'remember_token',
    ];

    /**
     * The attributes that should be cast to native types.
     *
     * @var array
     */
    protected $casts = [
        'email_verified_at' => 'datetime',
    ];


}

Device Controller :

<?php

namespace App\Http\Controllers;

use App\Device;

use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class DeviceController extends Controller{

    //register
    public function signupDevice(Request $request){


        //cant registed with the same email twice
        if(sizeof(Device::where('name','=',$request->query('name'))->get()) > 0)
            return response()->json(['name has already been taken'],500);



        $request->validate([
            'name' => 'required|string',
            'password' => 'required|string|confirmed']);

        $device =new Device(
            [

                'name'=>$request->name,
                'password'=>bcrypt($request->password)

            ]);
        $device->save();
        return response()->json([
            'message' => 'Successfully created device!'
        ], 201);
    }

    public function login(Request $request){

        //validate the data input
        $request->validate([
            'name' => 'required|string',
            'password' => 'required|string',]);


        //attempt returns true if the user is in the database
        $credentials = request(['name', 'password']);
        if(!Auth::guard('device')->attempt($credentials))
            return response()->json([
                'message' => 'Unauthorized'
            ], 401);

        //get the device
        $device = $request->user('device');

        //create token  PAT
        $tokenResult = $device->createToken('Personal Access Token');
        $token = $tokenResult->token;
        if ($request->remember_me)
            $token->expires_at = Carbon::now()->addWeeks(1);


        //save the token
        $token->save();

        return response()->json([
            'access_token' => $tokenResult->accessToken,
            'token_type' => 'Bearer',
            'expires_at' => Carbon::parse(
                $tokenResult->token->expires_at
            )->toDateTimeString()
        ],200);
    }

    public function index(Request $request)
    {

        return response()->json($request->user());
    }


}

Routes:

 //routes for device auth
    Route::group(
        [
            'prefix'=>'auth/device'
        ],function ()
        {
            Route::post('signup','DeviceController@signupDevice');
            Route::post('login','DeviceController@login');


         Route::group(
             [
                 'middleware'=>'device'
             ],function(){
              //all the routes that go  throught middleware
             Route::get('index','DeviceController@index');

           });
        });
2
protected $fillable suggestes Device to be a Model right? i didn't do Laravel for ages can't you do -> protected $table = "table_name"; to overrule the default table name ? - Raymond Nijland

2 Answers

0
votes

Okay so As I was doing this myself I ended up in a roadblock:

The tokens registred in the database may end up with duplicate user_id, because your two tables will have different auto-increment values. The token has a name property, so when a customer (in your case a device) that has the same user_id, you can differentiate them with the name.

To achieve what you are asking you need to declare another provider and another guard inside : App/config/auth.php

<?php
    'guards' => [
        'web' => [
            'driver' => 'session',
            'provider' => 'users',
        ],
        'user' => [
            'driver' => 'session',
            'provider' => 'users',
        ],
        'user-api'=>[
            'driver'=>'passport',
            'provider'=>'users',
            'hash'=>false,
        ],
        'customer' => [
            'driver' => 'session',
            'provider' => 'customers',
        ],
        'customer-api'=>[
            'driver'=>'passport',
            'provider'=>'customers',
            'hash'=>false,
        ],

    ],
...
'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => App\Models\User::class,
        ],
        'customers'=>[
            'driver'=>'eloquent',
            'model'=>App\Customer::class,
        ],
    ],
...

the new model need to implement new properties/class to make it work:

use Illuminate\Database\Eloquent\Model;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Passport\HasApiTokens;


//
class Customer extends Authenticatable
{
    use HasApiTokens, HasFactory, Notifiable;

    protected $guarded=['id'];
    protected $fillable = [
        /*fillable props*/
    ];
    //
    protected $hidden=[
        /*Hidden props*/
    ];
    //
    protected $casts=[
        /*Your casts*/
    ];

    public function getAuthPassword()
    {
        return $this->password;
    }
}

Now you need to prefix http request with the api middleware, inside App/Providers/RouteServiceProvider:

public function boot()
    {
        $this->configureRateLimiting();

        $this->routes(function () {
            Route::prefix('api')
                ->middleware('api')
                ->group(base_path('routes/api.php'));
    }

Then when you declare a route use the new auth middleware guard with the passport driver you declared:

Route::group( ['prefix' => 'customer','middleware' => ['auth:customer-api'] ],function(){

/*Authenticated staff route here*/

});  

When you log in you should use the auth guard with the session driver:

public function customerLogin(Request $request)
    {
        $validator = Validator::make($request->all(), [
            'email' => 'required|email',
            'password' => 'required',
        ]);

        if($validator->fails()){
            return response()->json(['error' => $validator->errors()->all()]);
        }

        if(auth()->guard('customer')->attempt(['email' => request('email'), 'password' => request('password')])){
            config(['auth.guards.api.provider' => 'customer']);
  
            $customer = Customer::select('customers.*')->find(auth()->guard('customer')->user()->id);
            $success =  $customer;
            $success['token'] =  $customer->createToken('Customer'.$customer->name,['customer'])->accessToken; 

            return response()->json($success, 200);
        }else{ 
            return response()->json(['error' => ['Email and Password are Wrong.']], 200);
        }
    } 

Here is the token output table data for different authentication

  

  {
        "id": "2a8526b24bd89a47f29474a86ba350c843cd4f7c5b0785c34d908efe00a4715c43502dbd9f789b83",
        "user_id": "19",
        "client_id": "15",
        "name": "user;SuperAdmin",
        "scopes": "[\"user\"]",
        "revoked": "0",
        "created_at": "2021-02-24 02:10:31",
        "updated_at": "2021-02-24 02:10:31",
        "expires_at": "2021-02-25 02:10:31"
    },
    {
        "id": "388792d1c191529c65f1fb67d58972d2b26aae19d99c8df1c2321ec100bedff96a38b7724626f1cb",
        "user_id": "53",
        "client_id": "15",
        "name": "Customer;[email protected]",
        "scopes": "[\"customer\"]",
        "revoked": "0",
        "created_at": "2021-02-24 02:10:28",
        "updated_at": "2021-02-24 02:10:28",
        "expires_at": "2021-02-25 02:10:28"
    }

I did not include scopes. But take note if you want to restrict route access you should implement them.

-1
votes

For example if your table name is tbl_user then you should create model as below and add model path inside config/auth.php

Model:
<?php

namespace App\model\Users;

use Illuminate\Database\Eloquent\Model;
use Laravel\Passport\HasApiTokens;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;

class UserModel extends Authenticatable
{
    use HasApiTokens, Notifiable;
    protected $table = 'tbl_user';
    public $timestamps = false;

}


config/auth.php
'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => App\model\Users\UserModel::class,
        ],
    ],