Laravel api authorization with api_token - php

I am trying to create a Laravel API project. So I have this project with basic laravel's scaffolding set up. In my user migration I have added:
$table->string('api_token', 60)->unique();
then in my User.php model i have added:
protected $fillable = [
'name', 'email', 'password','api_token'
];
Then in my api.php i have made a test route:
Route::group(['middleware' => ['auth:api']], function(){
Route::get('/test', 'ApiController#test');
});
in my Apicontroller:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class ApiController extends Controller
{
public function test(Request $request){
return response()->json(['name' => 'test']);
}
}
so now i type this : with my api_token
localhost/project1/public/api/test?api_token='hsvdvhvsjhvasdvas8871238'
It's not giving me the json data, instead it's redirecting to the logged in home page

localhost/project1/public/index.php/api/test?api_token='hsvdvhvsjhvasdvas8871238' would help.
If you want pretty urls, read the documentation: Pretty URLs

For laravel 5.2
Middleware/ApiAuthenticate
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Auth;
class ApiAuthenticate
{
public function handle($request, Closure $next, $guard = null)
{
if (Auth::guard($guard)->guest()) {
return response()->json(['status'=>'error','message'=>'token mismatch']);;
}
return $next($request);
}
}
Kernel.php add
protected $routeMiddleware = [
'autho' => \App\Http\Middleware\ApiAuthenticate::class,
];
routes.php
Route::group(['prefix'=>'api','middleware'=>'autho:api'], function(){
Route::get('aaa','Api\AAAController#index');
});

You would not have to write your own API middleware and routes if you use Laravel 5.3 or higher version.
Moreover, you can use the in-built Passport package to manage the access token, using oAuth2.
$http = new GuzzleHttp\Client;
$response = $http->post($apiUrl.'oauth/token', [
'form_params' => [
'grant_type' => 'password',
'client_id' => '2', //this can be generated when you setup Passport package or using artisan commands
'client_secret' => 'xxxxxxxxx', //this can be generated when you setup Passport package or using artisan commands
'username' => 'a#a.com',
'password' => 'test123',
'scope' => '',
],
]);
$responseData = json_decode($response->getBody(), true);
$token = $responseData['access_token']; //Now I have the token so I can call any protected routes
$response = $http->request('GET', $apiUrl.'api/v1/user', [
'headers' => [
'Accept' => 'application/json',
'Authorization' => 'Bearer '.$token,
],
]);
$responseData = json_decode($response->getBody(), true);
echo "Name of the user is: ".$responseData['name'];

Related

Laravel test kept failing due to wrong method

I have the following test to check my simple crud application in laravel.
I have my SalesManagersTest.php in tests/features
<?php
use App\Providers\RouteServiceProvider;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class SalesManagersTest extends TestCase
{
use RefreshDatabase;
public function test_can_see_index_screen()
{
$response = $this->get('/salesmanagers');
$response->assertStatus(200);
}
public function test_can_see_add_record_screen()
{
$response = $this->get('/salesmanagers/create');
$response->assertStatus(200);
}
public function test_can_add_new_record()
{
$response = $this->POST('/salesmanagers/create', [
'_token' => csrf_token(),
'full_name' => 'Test User',
'email' => 'test#example.com',
'telephone' => '0775678999',
'current_route' => 'Negombo',
'joined_date'=>'2022/09/12',
'comments' => 'Test comment'
]);
$response->assertStatus(200);
$response->assertRedirectedTo('salesmanagers');
}
}
The first two tests work well but the third test is giving me an error
but since I'm trying to insert new record the method has to be POST
this is my web.php
Route::get('/', [ SalesManagersController::class, 'index' ]);
Route::resource('salesmanagers', SalesManagersController::class);
Route::get('salesmanagers.create', [ SalesManagersController::class, 'create' ]);
Route::post('salesmanagers.create', [ SalesManagersController::class, 'store' ]);
What could be the the issue with my test?
The issue is your route definition. It appears you're using a route name for the URI of your route. Change your route to the following and try again:
Route::post('/salesmanagers', [SalesManagersController::class, 'store']);
Oh and POST your data to /salesmanagers not salesmanagers/create.

Issue in passport implementation in multi tenant laravel application

First things first, I am using Hyn-Multi Tenant in my laravel 6 application Where there is a central database [connection = system] handles multiple tenant database. So far this package has helped me a lot but my application needs passport implementation for apis which is not documented in the package.
However there are other tutorials which claim passport implementation on Hyn package. I followed them and able to create access token per tenant user.
This is my config/auth.php:
return [
'defaults' => [
'guard' => 'web',
'passwords' => 'system-users',
],
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'system',
],
'staff' => [
'driver' => 'session',
'provider' => 'staff',
],
'api' => [
'driver' => 'passport',
'provider' => 'staff',
'hash' => false,
],
'student' => [
'driver' => 'passport',
'provider' => 'student',
'hash' => false,
],
],
'providers' => [
'system' => [
'driver' => 'eloquent',
'model' => App\Models\System\User::class,
],
'staff' => [
'driver' => 'eloquent',
'model' => App\Models\Tenant\Staff::class,
],
'student' => [
'driver' => 'eloquent',
'model' => App\Models\Tenant\Student::class,
],
// 'users' => [
// 'driver' => 'database',
// 'table' => 'users',
// ],
],
My each tenant models uses UsesTenantConnection trait
This is my EnforceTenancy middleware
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Config;
class EnforceTenancy
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
Config::set('database.default', 'tenant');
return $next($request);
}
}
This is my AuthServiceProvider.php
public function boot()
{
$this->registerPolicies();
Passport::routes(null, ['middleware' => 'tenancy.enforce']);
// FOLLOWING CODE IS HAVING PROBLEM
//Passport::useTokenModel(OAuthAccessToken::class);
//Passport::useClientModel(OAuthClient::class);
//Passport::useAuthCodeModel(OAuthCode::class);
//Passport::usePersonalAccessClientModel(OAuthPersonalAccessClient::class);
$this->commands([
\Laravel\Passport\Console\InstallCommand::class,
\Laravel\Passport\Console\ClientCommand::class,
\Laravel\Passport\Console\KeysCommand::class,
]);
\Laravel\Passport\Passport::tokensExpireIn(\Carbon\Carbon::now()->addMinutes(10));
\Laravel\Passport\Passport::refreshTokensExpireIn(\Carbon\Carbon::now()->addDays(1));
}
So far all good, now I am going to explain in points,
When I call createToken('MyApp') I am able to generate token on tenant db, for example:
if (Auth::guard('staff')->attempt(['email' => $request->email, 'password' => $request->password])) {
$user = Auth::guard('staff')->user();
$auth_tokens = $user->createToken('MyApp');
$access_token = $auth_tokens->accessToken;
...
}
but to access login protected apis, I am sending bearer access token in header
window.axios
.get("/api/meta",{
headers: fetchAuthHeaders()
})
.then(response => {
if(true == response.data.status) {
var data = response.data.data;
this.school.name = data.school_meta.name;
this.school.logo = data.school_meta.logo;
} else{
alert(response.data.message);
}
})
api.php
Route::domain('{hostname}.lvh.me')->group(function () {
Route::middleware('tenant.exists')->group(function () {
Route::get('/get-oauth-secret', 'Tenant\MetaController#getOAuthData');
Route::post('validate-login','Tenant\AuthController#validateLogin');
Route::middleware(['auth:api'])->group(function (){
Route::get('meta','Tenant\AuthController#getMetaData'); //this api
});
});
});
I am getting response as {"message":"Unauthenticated."}
Once the token is generated in step 1, I copy this token and paste into postman's header section and uncomment the custom passport models in AuthServiceProvider.php as shown below
AuthServiceProvider.php
public function boot()
{
...
// UNCOMMENTED FOLLOWING CUSTOM PASSPORT MODELS
Passport::useTokenModel(OAuthAccessToken::class);
Passport::useClientModel(OAuthClient::class);
Passport::useAuthCodeModel(OAuthCode::class);
Passport::usePersonalAccessClientModel(OAuthPersonalAccessClient::class);
...
}
Now I can access api/meta route but while login and creating token I am getting error:
ErrorException: Trying to get property 'id' of non-object in file /home/winlappy1/Desktop/multi_tenancy/vendor/laravel/passport/src/PersonalAccessTokenFactory.php on line 98
I just want to know where I am going wrong, I know my explanation is quite ambiguous and confusing but thats all how I can explain my issue. I am ready to provide more clarification but I need to resolve this issue.
Try to add
\App\Http\Middleware\EnforceTenancy::class
into the beginning of $middlewarePriority array in Kernel.php
Also use Laravel Passport 9.1.0 which support multi Auth
Try to do this
#AuthServiceProvider
Add this
public function boot()
{
$this->registerPolicies();
This one is to check if the database is Tenant or not
$website = \Hyn\Tenancy\Facades\TenancyFacade::website();
if ($website != null) {
Passport::useClientModel(PassportClient::class);
Passport::useTokenModel(PassportToken::class);
Passport::useAuthCodeModel(PassportAuthCode::class);
Passport::usePersonalAccessClientModel(PassportPersonalAccessClient::class);
}
$this->commands([
\Laravel\Passport\Console\InstallCommand::class,
\Laravel\Passport\Console\ClientCommand::class,
\Laravel\Passport\Console\KeysCommand::class,
]);
\Laravel\Passport\Passport::tokensExpireIn(\Carbon\Carbon::now()->addMinutes(10));
\Laravel\Passport\Passport::refreshTokensExpireIn(\Carbon\Carbon::now()->addDays(1));
}
Along with these add The four models
Like this
Create four Models Which enforce the Tenants
use Hyn\Tenancy\Traits\UsesTenantConnection;
use Laravel\Passport\AuthCode;
class PassportAuthCode extends AuthCode
{use UsesTenantConnection;}
use Hyn\Tenancy\Traits\UsesTenantConnection;
use Laravel\Passport\Client;
class PassportClient extends Client
{use UsesTenantConnection;}
use Hyn\Tenancy\Traits\UsesTenantConnection;
use Laravel\Passport\PersonalAccessClient;
class PassportPersonalAccessClient extends PersonalAccessClient
{use UsesTenantConnection;}
use Hyn\Tenancy\Traits\UsesTenantConnection;
use Laravel\Passport\Token;
class PassportToken extends Token
{use UsesTenantConnection;}
Also use (tenancy.enforce) middleware Enforcetenancy
'tenancy.enforce' => \App\Http\Middleware\EnforceTenancy::class, $Routemiddleware kernel.php
EnforceTenancy.php middleware
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Config;
class EnforceTenancy
{
/**
* Handle an incoming request.
*
* #param Request $request
* #param Closure $next
*
* #return mixed
*/
public function handle($request, Closure $next)
{
Config::set('database.default', 'tenant');
return $next($request);
}
}
Force the tenant routes through tenancy.enforce middleware
Also publish the new migrations and migrate:fresh as new fields are added to the new passport

Laravel - Trying to get property 'token' of non-object", exception: "ErrorException"

I am using Angular-7 as frontend and Laravel-5.8 as backend for a web application.
ApiController.php
public function login(Request $request)
{
$request->validate([
'email' => 'required|string|email',
'password' => 'required|string',
'remember_me' => 'boolean'
]);
$credentials = request(['email', 'password']);
$credentials['active'] = 1;
$credentials['deleted_at'] = null;
if(!Auth::attempt($credentials))
return response()->json([
'message' => 'Unauthorized'
], 401);
$user = $request->user();
$res = User::with('roles')->find($user->id);
$tokenResult = $user->createToken('MyApp')->accessToken;
$token = $tokenResult->token;
if ($request->remember_me)
$token->expires_at = Carbon::now()->addWeeks(13);
$token->save();
return response()->json([
'access_token' => $tokenResult->accessToken,
'token_type' => 'Bearer',
'expires_at' => Carbon::parse($tokenResult->token->expires_at)->toDateTimeString(),
'user' => response()->json($res)->original
]);
}
From the Angular frontend, when I click on submit on the Login Page, it suppose to redirect to home page. But I got this error:
{message: "Trying to get property 'token' of non-object", exception: "ErrorException", file: "C:\xampp\htdocs\clientportal-app\backend\app\Http\Controllers\ApiController.php", line: 212, trace: Array(35)}
When I checked line 212 from Laravel ApiController from the code above, this is what I have:
$token = $tokenResult->token;
How do I resolve it?
Laravel Passport requires following configuration.
User model needs to use the HasApiTokens trait.
class User extends Authenticatable
{
use Notifiable, HasApiTokens;
}
Passport routes need to added to the boot() method of AuthServiceProvider
public function boot()
{
$this->registerPolicies();
Passport::routes();
}
And finally change the api driver to passport in auth config file.
'api' => [
'driver' => 'passport',
'provider' => 'users',
]
You need to migrate Passport tables
php artisan migrate
and generate encryption keys
php artisan passport:install
Clear cache with
php artisan config:cache
and serve your app
php artisan serve

Laravel 5.6 Passport oAuth2 can't make request with Guzzle

I have a fresh project on Laravel 5.6, where I'm trying to study and understand API Auth with Passport. I'm trying to do that, and after that to make a Javascript application from where I'll access that API. So, API for first-party applications.
I've installed and registered all routes and setup specific to passport, and also installed Guzzle.
I looked for some tutorials and now I'm with that code :
RegisterController.php
<?php
namespace App\Http\Controllers\Api\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use Laravel\Passport\Client;
use App\User;
class RegisterController extends Controller
{
use IssueTokenTrait;
private $client;
public function __construct(){
$this->client = Client::find(1); //Client 1 is a Laravel Password Grant Client token from my DB (when I wrote php artisan passport:install
}
public function register(Request $request){
$this->validate($request, [
'name' => 'required',
'email' => 'required|email|unique:users,email',
'password' => 'required|min:3',
'password_confirmation' => 'required|same:password'
]);
$user = User::create([
'name' => request('name'),
'email' => request('email'),
'password' => bcrypt(request('password'))
]);
return $this->issueToken($request, 'password');
}
}
It uses issueToken function from that Trait :
IssueTokenTrait.php
namespace App\Http\Controllers\Api\Auth;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use GuzzleHttp\Client;
trait IssueTokenTrait{
public function issueToken(Request $request, $grantType, $scope = ""){
$params = [
'grant_type' => $grantType,
'client_id' => $this->client->id,
'client_secret' => $this->client->secret,
'scope' => $scope
];
$params['username'] = $request->username ?: $request->email;
$request->request->add($params);
$proxy = Request::create('oauth/token', 'POST');
return Route::dispatch($proxy);
}
}
**NOW THE PROBLEM : **
Everything works perfect. I can register, I have an access token which works on protected with auth routes, and doesn't work when I give a wrong token.
I read the documentation of Passport in Laravel 5.6 and all examples use GuzzleHttp to make requests inside controller method, and I have tried to rewrite my code using Guzzle instead of Request::dispatch.
So, I found in multiple sources, in documentation as well code with different but also same logic implementation, so my IssueTokenTrait now looks like :
<?php
namespace App\Http\Controllers\Api\Auth;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use GuzzleHttp\Client;
trait IssueTokenTrait{
public function issueToken(Request $request, $grantType, $scope = ""){
$params = [
'grant_type' => $grantType,
'client_id' => $this->client->id,
'client_secret' => $this->client->secret,
'scope' => $scope
];
$params['username'] = $request->username ?: $request->email;
$url = url('/oauth/token');
$headers = ['Accept' => 'application/json'];
$http = new GuzzleHttp\Client;
$response = $http->post($url, [
'headers' => $headers,
'form_params' => [
'grant_type' => 'password',
'client_id' => $this->client->id,
'client_secret' => $this->client->secret,
'username' => $request->email,
'password' => $request->password
],
]);
return json_decode((string)$response->getBody(), true);
}
}
And there is how my app gets broken.
When I make a POST request to /api/register from POSTMAN now, it just not returns me a response, like please wait... and that's it. And if I restart my server, it returns me :
[Mon Aug 20 11:29:16 2018] Failed to listen on 127.0.0.1:8000 (reason: Address already in use).
So, it looks like it makes this request, but it not returns the response, or it goes in a infinite loop.
I get stuck for a day with that problem, and really it looks like some mystic here. Because all parameters and values are like it was with Route::dispatch, just the method of making this HTTP request changes.
There are 2 options:
Try to run another Artisan server process with different port (ex: 8001) and guzzle to it.
Using personal access token instead, using createToken to generate access token.

Laravel 5.4 Passport -- How to redirect to client dashboard after getting user info from the access token

In Laravel Passport, after getting the access token from the server, able to access the user information from the client. Now I am stuck with how to redirect it to client dashboard?
Here is my callback function:
Route::get('/callback', function (Illuminate\Http\Request $request) {
$http = new GuzzleHttp\Client;
$response = $http->post('http://localhost.server:8080/oauth/token', [
'form_params' => [
/* Auth code grant*/
'client_id' => '<client_id>',
'client_secret' => '<client_secret>',
'grant_type' => 'authorization_code',
'redirect_uri' => 'http://localhost.client:8000/callback',
'code' => $request->code,
],
]);
$auth_grant = json_decode((string) $response->getBody(), true);
$token_type = $auth_grant['token_type'];
$access_token = $auth_grant['access_token'];
$user_auth = $http->request('GET', 'http://localhost:8080/api/user', [
'headers' => [
'Accept' => 'application/json',
'Content-Type' => 'application/json',
'Authorization' => $token_type.' '.$access_token,
],
]);
$usrAuth = json_decode((string) $user_auth->getBody(), true);
});
Note:
Before redirecting to Dashboard, want to store the user info in the Auth and later wants to verify every other route through the VarifyUser middleware. That authenticating the user through Auth::check.
Route::group(['middleware' => ['verify_user', 'language']], function(){
// If you want to check loggining user, have to use 'verify_user' middleware
route::group(['namespace' => 'Index'], function(){
Route::get('/index', 'IndexController#index');
});
route::group(['namespace' => 'Group'], function(){
// Group page
Route::get('/group-registration', 'GroupController#index');
// Register group
Route::post('/registerGroup', 'GroupController#registerGroup');
});
}
VerifyUser middleware:
<?php
namespace App\Http\Middleware;
// Requirement
use Illuminate\Support\Facades\Auth;
use Closure;
class VerifyUser
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if(!Auth::check()){
return redirect('/login');
}
return $next($request);
}
}
Add response redirect at the end of methods
Route::get('/callback', function (Illuminate\Http\Request $request) {
// callback handle
....
// here, add this to redirect after callback success
return redirect('dashboard');
});

Categories