I am new to laravel and trying to do a simple Authentication project using the laravel/passport package provided by the laravel team and I have no problem with sending the token via a httponly cookie and receiving it from a frontend framework Vuejs. I am able to get the authentication working by storing the token into a localstorage on the browser but all the blogs and documentations I have read suggest not to use localstorage for storing a jwt token and most of them suggest to use httponly cookie because it is not accessible from javascript.
and for my layers of authentication use
a proxyRequest util class
an Authcontroller
I do not use the default User model I have a person model
I have a Cors middleware for allowing requests
proxyRequest class
<?php
namespace App\Utilities;
// use Illuminate\Support\Facades\Http;
class ProxyRequest {
public function grantPasswordToken(string $email, string $password)
{
$params = [
'grant_type' => 'password',
'username' => $email,
'password' => $password,
];
return $this->makePostRequest($params);
}
public function refreshAccessToken()
{
$refreshToken = request()->cookie('refresh_token');
abort_unless($refreshToken, 403, 'Your refresh token is expired.');
$params = [
'grant_type' => 'refresh_token',
'refresh_token' => $refreshToken,
];
return $this->makePostRequest($params);
}
protected function makePostRequest(array $params)
{
$params = array_merge([
'client_id' => config('services.passport.password_client_id'),
'client_secret' => config('services.passport.password_client_secret'),
'scope' => '',
], $params);
$proxy = \Request::create('oauth/token', 'post', $params);
$resp = json_decode(app()->handle($proxy)->getContent());
$this->setHttpOnlyCookie($resp->refresh_token);
return app()->handle($proxy)->getContent();
return $resp;
}
protected function setHttpOnlyCookie(string $refreshToken)
{
cookie()->queue(
'refresh_token',
$refreshToken,
14400, // 10 days
null,
null,
false,
true // httponly
);
}
}
Authcontroller
<?php
namespace App\Http\Controllers\Api;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Person;
// use App\Person;
// use App\Person;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Auth;
use Cookie;
use App\Utilities\ProxyRequest;
class AuthController extends Controller
{
protected $proxy;
public function __construct(ProxyRequest $proxy)
{
$this->proxy = $proxy;
}
public function user(Request $request)
{
if (Auth::guard('api')->check()) {
return $request->user('api');
}
return "not auth";
}
public function register(Request $request)
{
// $this->validate(request(), [
// 'name' => 'required',
// 'email' => 'required|email',
// 'password' => 'required',
// ]);
$user = Person::create([
'f_name' => $request->name,
'email' => $request->email,
'password' => Hash::make($request->password),
]);
$resp = $this->proxy->grantPasswordToken(
$user->email,
$request->password
);
// return response([
// 'token' => $resp->access_token,
// 'expiresIn' => $resp->expires_in,
// 'message' => 'Your account has been created',
// ], 201);
return response()->json($resp);
}
public function check() {
return "Authenticated";
}
public function refreshToken()
{
$resp = $this->proxy->refreshAccessToken();
return response([
'token' => $resp->access_token,
'expiresIn' => $resp->expires_in,
'message' => 'Token has been refreshed.',
], 200);
}
public function logout(Request $request)
{
$token = $request->user()->token();
$token->delete();
// remove the httponly cookie
cookie()->queue(cookie()->forget('refresh_token'));
return response([
'message' => 'You have been successfully logged out',
], 200);
return $request->all();
}
public function login(Request $request)
{
$user = Person::where('email', $request->email)->first();
abort_unless($user, 404, 'User not found.');
abort_unless(
Hash::check($request->password, $user->password),
403,
'Password or mail address not correct.'
);
$resp = $this->proxy->grantPasswordToken($user->email, $request->password);
return response([
'token' => $resp->access_token,
'expiresIn' => $resp->expires_in,
'message' => 'You have been logged in',
], 200);
}
}
routes
Route::middleware('guest')->group(function () {
Route::post('register', 'Api\AuthController#register')->name('register');
Route::post('login', 'Api\AuthController#login')->name('login');
Route::post('refresh-token', 'Api\AuthController#refreshToken')->name('refreshToken');
});
Route::middleware('auth:api')->group(function () {
Route::post('logout', 'Api\AuthController#logout')->name('logout');
});```
middlewares
protected $middleware = [
\App\Http\Middleware\addCookieToHeader::class,
\App\Http\Middleware\CheckForMaintenanceMode::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
\App\Http\Middleware\TrustProxies::class,
\App\Http\Middleware\Cors::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
];
when I hit the login and register routes I am able to generate a token and could find it in my chrome (Application -> cookies with the name refresh token) but if I try to logout
i just get a response that says {message: "UnAuthenticated"} with a 401 error.
I couldnt find anything regarding this on the internet.
Related
I am made an App with Laravel 9 back-end and ReactJS front-end and get a 401 unauthorized error even I am successfully logged-in in to my App. For Authentication I am using Laravel's build-in Passport and Cross-Domain Middleware.
Code is in api.php router and looks like below:
Route::group(['prefix' => 'users', 'middleware' => 'CORS'], function ($router) {
Route::post('/register', [AuthController::class, 'register'])->name('register.auth');
Route::post('/login', [AuthController::class, 'login'])->name('login.auth');
Route::get('/userdata', [AuthController::class, 'userData'])->name('userdata.auth');
Route::get('/checkifemailverified/{token}', [AuthController::class, 'checkIfEmailVerified'])->name('user.verify');
Route::get('/logout', [AuthController::class, 'logout'])->name('logout.auth');
Route::get('/me', [AuthController::class, 'me'])->name('me.auth');
});
And here is a AuthController:
<?php
namespace App\Http\Controllers;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Hash;
use Auth;
use App\Models\User;
use App\Models\UserVerify;
use Validator;
use Illuminate\Support\Str;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Carbon;
use Mail;
use Illuminate\Support\Facades\View;
class AuthController extends Controller
{
//private $apiToken;
protected $user;
public function __construct()
{
//$this->apiToken = uniqid(base64_encode(Str::random(40)));
$this->middleware("auth:api",["except" => ["login","register","logout","verifyAccount","checkIfEmailVerified","me"]]);
$this->user = new User;
}
/**
*
* #return \Illuminate\Http\Response
*/
public function login(Request $request){
$validator = Validator::make($request->all(),[
'email' => 'required|string',
'password' => 'required|min:8',
]);
if($validator->fails()){
return response()->json([
'success' => false,
'message' => $validator->messages()->toArray()
], 500);
}
//User check
$credentials = $request->only(["email","password"]);
$user = User::where('email',$credentials['email'])->first();
if($user->is_active===1){
if(auth()->attempt($credentials)){
//$user = Auth::user();
if($user->email_verified_at !== NULL){
if (DB::table('domains')
->select('domain', 'valid_before')
->where('domain', $user->domain)
->whereDate('valid_before_at', '<=', Carbon::now())
->count()){
return response()->json([
'success' => false,
'data' => 'Username and password do not match or domain subscription is not valid or expired!'
], 200);
} else {
// Login and "remember" the given user...
//Auth::login($user, true);
//Setting login response
$accessToken = auth()->user()->createToken('authToken')->accessToken;
/*$success['token'] = $this->apiToken;
$success['name'] = $user->name;
return response()->json([
'success' => true,
'data' => $success
], 200);*/
$responseMessage = "Login Successful";
return $this->respondWithToken($accessToken,$responseMessage,auth()->user());
}
} else {
return response()->json([
'success' => false,
'data' => 'Please Verify Email!'
], 200);
}
} else {
//$success['success'] = false;
return response()->json([
'success' => false,
'data' => 'Username and password do not match or domain subscription is not valid or expired!'
], 200);
}
}
}
public function register(Request $request){
$validator = Validator::make($request->all(),[
'email' => 'required|string',
'name' => 'required|string',
'domain' => 'required|string',
'password' => 'required|min:8',
]);
if($validator->fails()){
return response()->json([
'success' => false,
'message' => $validator->messages()->toArray()
], 500);
}
DB::beginTransaction();
try {
DB::table('domains')->insert([
['domain' => $request->domain, 'valid_before_at' => date('Y-m-d H:i:s', strtotime("+30 day")), 'created_at' => date('Y-m-d H:i:s'), 'updated_at' => date('Y-m-d H:i:s')]
]);
DB::table('users')->insert([
['name' => $request->name, 'email' => $request->email, 'password' => Hash::make($request->password), 'created_at' => date('Y-m-d H:i:s'), 'updated_at' => date('Y-m-d H:i:s'), 'domain' => $request->domain]
]);
DB::commit();
// all good
/*return response()->json([
'success' => true,
'data' => 'User and domain is now created!'
], 200);*/
try {
$id = DB::table('users')
->select('id')
->where('email', $request->email)
->first()->id;
$token = Str::random(64);
DB::table('users_verify')->insert([
['user_id' => $id, 'token' => $token, 'created_at' => date('Y-m-d H:i:s'), 'updated_at' => date('Y-m-d H:i:s')]
]);
Mail::send('emails.userverify', ['token' => $token], function($message) use($request){
$message->from(env('MAIL_FROM_ADDRESS'), env('MAIL_FROM_NAME'));
$message->to($request->email);
$message->subject('Email Verification Mail');
});
return response()->json([
'success' => true,
'data' => 'User and domain is now created!'
], 200);
} catch (\Exception $e) {
// something went wrong
return response()->json([
'success' => false,
'data' => $e->getMessage()
], 200);
}
} catch (\Exception $e) {
DB::rollback();
// something went wrong
return response()->json([
'success' => false,
'data' => $e->getMessage()
], 200);
}
}
/**
* Write code on Method
*
* #return response()
*/
public function verifyAccount($token)
{
$verifyUser = UserVerify::where('token', $token)->first();
$message = 'Sorry your email cannot be identified.';
if(!is_null($verifyUser) ){
$user = $verifyUser->user;
if($user->email_verified_at == null) {
$verifyUser->user->email_verified_at = date('Y-m-d H:i:s');
$verifyUser->user->save();
$message = "Your e-mail is verified. You can now login.";
} else {
$message = "Your e-mail is already verified. You can now login.";
}
}
return redirect(env('APP_UI_URL').'/verifyemail?token='.$token);
}
/**
* Write code on Method
*
* #return response()
*/
public function checkIfEmailVerified($token)
{
$verifyUser = UserVerify::where('token', $token)->first();
$message = 'Sorry your email cannot be identified.';
if(!is_null($verifyUser) ){
$user = $verifyUser->user;
if($user->email_verified_at == null) {
$message = "Your e-mail is verified. You can now login.";
} else {
$message = "Your e-mail is already verified. You can now login.";
}
}
return response()->json([
'success' => true,
'data' => $message
], 200);
}
/**
* Logout the current user and revokes its crenedials.
*
* #return response()
*/
public function logout(Request $request){
$user = Auth::guard("api")->user()->token();
$user->revoke();
$responseMessage = "successfully logged out";
return response()->json([
'success' => true,
'message' => $responseMessage
], 200);
}
/**
* Get the user's profile.
*
* #param Request $request
* #return Response
*/
public function userData(Request $request)
{
$data = Auth::guard("api")->user();
//var_dump($user);
return $data;
}
/**
* Get the user's profile name.
*
* #param Request $request
* #return Response
*/
public function me(Request $request)
{
$data = Auth::guard("api")->user();
//var_dump($data);
return $data;
}
}
Purpose is to get logged-in users details for ReactJS GUI with userdata route and/or me router user name E.g Matti Kiviharju.
Also if anyone wants to see whole source codes of am App there re publically here in my GIT repositories in Atlassian Bitbucket for investigation:
React Front-end: https://bitbucket.i4ware.fi/projects/LOG/repos/login-form/browse?at=refs%2Fheads%2Fmatti_0006
Laravel Back-end: https://bitbucket.i4ware.fi/projects/SAAS/repos/saas-app/browse?at=refs%2Fheads%2Fmatti_0008
However, right answer for this that solves a problem helps multiple people, because source codes is available on our public GIT repos.
I created a Laravel App. It could access through web, for admin's web page, and api for user's page. I created user's page using Vue so it needs API.
Doing so, I need two Auth Controller. One is automatically created using laravel scaffolding, for the web. And the other is created manually for user's login via token.
I did create the AuthController for API. This is the controller.
<?php
namespace App\Http\Controllers\API;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use App\User;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Facades\Auth;
class AuthController extends Controller
{
//untuk login
public function login(Request $request)
{
//validasi data
$this->validate($request, [
'email' => 'email',
'username' => 'string',
'password' => 'required'
]);
//login dapat menggunakan email atau username
$user = User::where('email', '=', $request->email)
->orWhere('username', '=', $request->username)->first();
// $username = User::where('username', $request->username)->first();
// dd($username);
$status = "error";
$message = "";
$data = null;
$code = 401;
// echo (gettype($email));
// echo(gettype($username));
// echo($email);
if($user){
if (Hash::check($request->password, $user->password)){
$user->generateToken(); //generated 60 random string
$status = 'success';
$message = 'Login Success';
//tampilkan data user menggunakan method to Array
$data = $user->toArray();
$code = 200;
}
else{
$message = "Login gagal, password salah";
}
}
else {
$message = "Login gagal, username atau email salah";
}
return response()->json([
'status' => $status,
'message' => $message,
'data' => $data,
], $code);
}
//untuk registrasi
public function register(Request $request)
{
$validator = Validator::make($request->all(),
[
'name' => 'required|string|max:255',
'email' => 'required|string|email|max:255|unique:users',
'password' => 'required|string|min:6',
'username' => 'string'
]);
if ($validator->fails()){
$errors = $validator->errors();
return response()->json([
'data' => [
'message' => $errors,
]
],400);
}
else{
$user = User::create([
'name' => $request->name,
'email' => $request->email,
'password' => Hash::make($request->password),
'username' => $request->username,
'roles' => json_encode(['CUSTOMER'])
]);
if ($user){
$user->generateToken();
$status = "success";
$message = "Register berhasil!";
$data = $user->toArray();
$code = 200;
}
else{
$message = "Register gagal";
}
return response()->json([
'status' => $status,
'message' => $message,
'data' => $data
], $code);
}
}
//untuk logout
public function logout(Request $request)
{
//get authenticated user data
$user = Auth::user();
if($user){
$user->api_token = null; //delete user's token
$user->save();
}
return response()->json([
'status' => 'success',
'message' => 'logout success,
'data' => null,
], 200);
}
}
Login and Register method is worked. But when i try to run Logout method, i get AuthenticationException. I tried to debug the $user variable at Logout method, it return null. So, that's why i get the error. My question is, how i solve it? Is it even possible to create two different authentication controller in laravel?
For clarity, this is my api.php
//Routing bersifat publik
Route::prefix('v1')->group(function (){
Route::post('login', 'API\AuthController#login');
Route::post('register', 'API\AuthController#register');
Route::get('books', 'API\ApiBookController#index');
Route::get('book/{id}', 'API\ApiBookController#view')
->where('id', '[0-9]+');
Route::resource('categories', 'API\ApiCategoryController')
->except(['create', 'update']);
//Routing bersifat private
Route::middleware('auth:api')->group(function (){
Route::post('logout', 'API\AuthController#logout');
});
});
Logout method response:
{
"status": "error",
"message": "Unauthenticated.",
"data": null,
"errors": {
"exception": "Illuminate\\Auth\\AuthenticationException",
"trace": [
"#0 D:\\xampp\\htdocs\\book-store\\vendor\\laravel\\framework\\src\\Illuminate\\Auth\\Middleware\\Authenticate.php(68): Illuminate\\Auth\\Middleware\\Authenticate->unauthenticated(Object(Illuminate\\Http\\Request), Array)"
]
}
}
Thank you for your time and consideration.
config\auth.php
'defaults' => [
'guard' => 'web',
'passwords' => 'users',
],
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'token',
'provider' => 'users',
'hash' => false,
],
],
*Note: default guard in config\auth.php is web so i can log in to the web.
Use auth guard..
$user=Auth::guard('api')->user();
you have to specific what user should be logout in your controller in the apiController
you should change
$user = Auth::user();
to
$user = Auth::guard('api')->user();;
then logout him.
It keeps giving failed via my controller when trying to make the post request. I'm trying to make a file upload and storing that file name associated with the user into my db. I'm not sure what I'm doing wrong here, I've tried many ways to fix this but to no avail as I've hit a wall. I believe it may be the way my code's written in my controller but I'm not too sure.
The error I'm getting in the logs is Call to a member function photos() on null which means
auth()->user() is not detecting the authenticated user and there lies the problem which begs the question - how? I'm logged in using correct credentials without issues. How come I can't validate in a separate controller?
What am I doing wrong and how can I fix this?
Note: My React.js and Laravel code bases are separated.
Here's my react form submission:
handleSubmit(e) {
e.preventDefault();
console.log("here in submitHandler()");
let access_token = Cookies.get("access_token").slice(13, -8);
const headers = {
Authorization: `Bearer ${access_token}`
}
console.log(this.state.file_path);
axios.post('http://myendpoint/api/auth/dashboard', this.state.file_path, {headers})
.then(response => {
console.log(response);
}).catch(error => {
console.log(error);
})
};
Here's my FileUploadController.php:
public function uploadTest(Request $request) {
if(auth()->user()) {
auth()->user()->photos()->create([
'file_path' => $request->file('fileToUpload')->getClientOriginalExtension()
]);
return response()->json($request->session()->get("user"));
}
return "failed";
}
Here's my User model:
public function photos() {
return $this->hasMany(Photo::class);
}
Here's my Photo model:
public function user() {
return $this->belongsTo(User::class);
}
Here's my auth for creating user and logging in (AuthController.php):
public function signup(Request $request) {
$request->validate([
'name' => 'required|string',
'email' => 'required|string|email|unique:users',
'password' => 'required|string|confirmed'
]);
$user = new User([
'name' => $request->name,
'email' => $request->email,
'password' => bcrypt($request->password)
]);
$user->save();
return response()->json([
'message' => 'Successfully created user!'
], 201);
}
public function login(Request $request) {
$request->validate([
'email' => 'required|string|email',
'password' => 'required|string',
'remember_me' => 'boolean'
]);
$credentials = request(['email', 'password']);
if(!Auth::attempt($credentials))
return response()->json([
'message' => 'Unauthorized'
], 401);
$user = $request->user();
$tokenResult = $user->createToken('Personal Access Token');
$token = $tokenResult->token;
if ($request->remember_me)
$token->expires_at = Carbon::now()->addWeeks(1);
$token->save();
$request->session()->put("user", $user);
return response()->json([
'access_token' => $tokenResult->accessToken,
'token_type' => 'Bearer',
'expires_at' => Carbon::parse(
$tokenResult->token->expires_at
)->toDateTimeString(),
$user
]);
}
I do have trouble in setting password reset facility for custom user table (suppose 'customers' table). It was successful to generate a token for password reset but unable to do the reset because of Laravel considering its default table 'users', not the table I suppose to do reset. Changing default table users to customers in Config\Auth.php generate validation error
Passwords must be at least six characters and match the confirmation.
I just copy/pasting what I did so far
<?php
/*
Code Done by Arun
*/
namespace App\Http\Controllers\API\ResetPassword;
use App\Http\Controllers\API\Controller;
use Illuminate\Http\Request;
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
use Illuminate\Foundation\Auth\ResetsPasswords;
use Illuminate\Support\Facades\Password;
use App\Model\Customer;
use Laravel\Passport\Client;
use Illuminate\Support\Facades\Auth;
class ResetPasswordController extends Controller
{
use ResetsPasswords;
public function __construct()
{
$this->middleware('api');
}
protected function guard()
{
return Auth::guard('api');
}
public function ResetPassword(Request $request)
{
$v = validator($request->only('email', 'token', 'password','confirm_password'), [
'token' => 'required|string|max:255',
'email' => 'required|string|email|max:255',
'password' => 'required|string',
'confirm_password' => 'required|string|min:6',
]);
if ($v->fails()) {
return response()->json(["error"=>$v->errors()->all()], 400);
}
else
{
$response = $this->broker()->reset(
$this->credentials($request), function ($user, $password) {
$this->resetPassword($user, $password);
});
}
return $response == Password::PASSWORD_RESET
? $this->sendResetResponse($response)
: $this->customResetFailedResponse($request, $response);
// $client = Client::where('password_client', 1)->first();
// $request->request->add([
// 'grant_type' => 'password',
// 'client_id' => $client->id,
// 'client_secret' => $client->secret,
// 'username' => $request->email,
// 'password' => $request->password,
// 'scope' => null,
// ]);
// // Fire off the internal request.
// $proxy = Request::create(
// 'oauth/token',
// 'POST'
// );
// return $response ;
}
}
Any Laravel expert can help me with the problem?
Im finally able to figure this out, im just posting answer may help others come across the same problem.
<?php
namespace App\Http\Controllers\API\Auth;
use App\Http\Controllers\API\Controller;
use Illuminate\Http\Request;
use Illuminate\Foundation\Auth\ResetsPasswords;
use Illuminate\Support\Facades\Password;
use App\Model\Customer;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Auth;
use GuzzleHttp\Client;
use DB;
class ResetPasswordController extends Controller
{
use ResetsPasswords;
protected $confirmEmail = false;
public function __construct()
{
}
public function resetPasswordCustomer(Request $request)
{
//Custome password reset mail has done in customer model
$v = validator($request->only('token', 'password','password_confirmation'), [
'token' => 'required|string|max:255',
'password' => 'required|string',
'password_confirmation' => 'required|string|min:6',
]);
if ($v->fails()) {
return response()->json(["error"=>$v->errors()->all()], 400);
}
else
{
$request->merge(['email'=> $this->checkTokenAgainstHashedValue($request->token)]);//Add new property to request..
$response = $this->broker()->reset(
$this->credentials($request), function ($user, $password) {
$this->resetPassword($user, $password);
});
}
return $response == Password::PASSWORD_RESET
? $this->authToken($request)
: $this->customResetFailedResponse($request, $response);
return $response ;
}
protected function checkTokenAgainstHashedValue($token)
{
$resetTable = DB::table('password_reset_customers')->select('token','email')->get();
foreach ($resetTable as $value) {
if (Hash::check($token, $value->token)) {
return $value->email;
}
}
return null;
}
protected function authToken(Request $request)
{
$http = new Client();
$response = $http->post('http://abcd.com/oauth/token', [
'form_params' => [
'grant_type' => 'password',
'client_id' => 2,
'client_secret' =>'qazrxGbDwrwbYXwbEbbkUFNO1zGB3eFYQN3AbG3m',
'username' => Auth::user()->email,
'password' => $request->password,
'scope' => '',
],
]);
return json_decode((string) $response->getBody(), true);
}
public function broker()
{
return Password::broker('customers');
}
}
I have three types of Authenticatable model and I need to have separate JWT authentication for each. Let me explain more about my issue.
I'm using MongoDB as my database and Laravel MongoDB is the package that I use.
User, Admin, and ServiceProvider are my models.
To having JWT auth in Laravel I use jwt-auth package. It's ok with user model (collection). when I want to use JWT with any of other models It not work and do everything with user again.
I search a lot an I found out that to change the provider user model I can use Config::set(); method like below,
Config::set('jwt.user', Admin::class);
Config::set('auth.providers.users.model', Admin::class);
But no effect on JWT auth. (I checked the value of 'jwt.user' and 'auth.providers.users.model' with Config::get() method and returned it, It has been changed to 'App\Admin').
Need to say, My codes are as simple as possible according to the documentation of the package.
Here is my UserController code:
class UserController extends Controller
{
public function login(Request $request)
{
$validator = Validator::make($request->all(), [
'email' => 'required|string|email|max:255',
'password' => 'required|min:6'
]);
if ($validator->fails()) {
return response()->json($validator->errors());
}
$credentials = $request->only('email', 'password');
try {
if (!$token = JWTAuth::attempt($credentials)) {
return response()->json(['error' => 'invalid_credentials'], 401);
}
} catch (JWTException $e) {
return response()->json(['error' => 'could_not_create_token'], 500);
}
$user = User::where('email', $request->email)->first();
return response()->json([
'user' => $user,
'token' => $token
]);
}
public function register(Request $request)
{
$validator = Validator::make($request->all(), [
'email' => 'required|string|email|max:255|unique:users',
'phone' => 'required|valid_phone|unique:users',
'password' => 'required|min:6',
'first_name' => 'required',
'last_name' => 'required',
]);
if ($validator->fails()) {
return response()->json($validator->errors());
}
User::create([
'phone' => $request->get('phone'),
'first_name' => $request->get('first_name'),
'last_name' => $request->get('last_name'),
'city_abbr' => $request->get('city_abbr'),
'email' => $request->get('email'),
'password' => bcrypt($request->get('password')),
]);
$user = User::first();
$token = JWTAuth::fromUser($user);
return response()->json([
'user' => $user,
'token' => $token
]);
}
}
And my AdminController:
class AdminController extends Controller
{
public function login(Request $request)
{
$validator = Validator::make($request->all(), [
'email' => 'required|string|email|max:255',
'password' => 'required|min:6'
]);
if ($validator->fails()) {
return response()->json($validator->errors());
}
$credentials = $request->only('email', 'password');
Config::set('jwt.user', Admin::class);
Config::set('auth.providers.users.model', Admin::class);
try {
if (!$token = JWTAuth::attempt($credentials)) {
return response()->json(['error' => 'invalid_credentials'], 401);
}
} catch (JWTException $e) {
return response()->json(['error' => 'could_not_create_token'], 500);
}
$admin = Admin::where('email', $request->email)->first();
return response()->json([
'admin' => $admin,
'token' => $token
]);
}
public function register(Request $request)
{
$validator = Validator::make($request->all(), [
'email' => 'required|string|email|max:255|unique:admins',
'phone' => 'required|valid_phone|unique:admins',
'password' => 'required|min:6',
'name' => 'required',
]);
if ($validator->fails()) {
return response()->json($validator->errors());
}
$admin = Admin::create([
'phone' => $request->get('phone'),
'name' => $request->get('name'),
'access' => $request->get('access'),
'email' => $request->get('email'),
'password' => bcrypt($request->get('password')),
]);
Config::set('jwt.user', Admin::class);
Config::set('auth.providers.users.model', Admin::class);
$token = JWTAuth::fromUser($admin);
return response()->json([
'admin' => $admin,
'token' => $token
]);
}
}
Am I wrong in somewhere?
Is there any solution for this?
Update:
To be sure about MongoDB functionality, I test all of above doings with a relational database, actually MySQL. Nothing changed!
JWTAuth generates token but when I run toUser method with any models except User, it returns null!
Any solution will be appreciated.
Here is what you must fo to add multi auth ability with JWT to my project.
In tymon JWT auth package. In JWTAuthServiceProvider, Change Tymon\JWTAuth\JWTAuth and Tymon\JWTAuth\Providers\User\UserInterface definition type from singleton to bind in bootBindings method.
Defined a new middleware and below code is its handle method:
public function handle($request, Closure $next){
if (!$request->header('Auth-Type')) {
return response()->json([
'success' => 0,
'result' => 'auth type could not found!'
]);
}
switch ($request->header('Auth-Type')) {
case 'user':
$auth_class = 'App\User';
break;
case 'admin':
$auth_class = 'App\Admin';
break;
case 'provider':
$auth_class = 'App\ServiceProvider';
break;
default:
$auth_class = 'App\User';
}
if (!Helpers::modifyJWTAuthUser($auth_class))
return response()->json([
'status' => 0,
'error' => 'provider not found!'
]);
return $next($request); }
Defined a function with name modifyJWTAuthUser in Helpers and here is its inner:
public static function modifyJWTAuthUser($user_class){
if (!$user_class ||
(
$user_class != 'App\User' &&
$user_class != 'App\Admin' &&
$user_class != 'App\ServiceProvider'
))
return false;
try {
Config::set('jwt.user', $user_class);
Config::set('auth.providers.users.model', $user_class);
app()->make('tymon.jwt.provider.user');
return true;
} catch (\Exception $e) {
return false;
} }
Introduced another $routeMiddleware like below in Kernel.php:
...
'modify.jwt.auth.user' => ChangeJWTAuthUser::class,
and the last step, Adding 'modify.jwt.auth.user' middleware to the routes that you want.
But even with this steps, You must have encountered a new issue. It was about getting the auth token by credentials in login and getting auth user from the token. (It seems that changing config value not effect on JWTAuth::attempt($credentials) and $this->auth->authenticate($token))
To solve the getting auth user from the token issue:
Create a new middleware CustomGetUserFromTokenwhich extends of Tymon'sjwt.authmiddleware, I meanGetUserFromTokenand in line 35, and **replace**$user = $this->auth->authenticate($token);with$user = JWTAuth::toUser($token);`
And to solve getting the auth token by credentials in login issue:
At first, Find the auth user and after that, check the user existence and valid the password with Hash::check() method, if these conditions return true, Generate a token from the user. Here is login code:
$admin = Admin::where('email', $request->email)->first();
if (!$admin || !Hash::check($request->get('password'), $admin->password)) {
return response()->json([
'success' => '0',
'error' => 'invalid_credentials'
], 401);
}
I'm not sure about this way but I think it's true until finding a correct way to do!
Conclusion:
Having multi JWT auth ability in Laravel perhaps have many other ways to do but I did like this and shared it to be helpful.
I think the only important point of this issue was app()->make('tymon.jwt.provider.user');, the ability to remake user provider after config values change.
Any other solutions will be appreciated.
You should use just one model (actually table) for authentication. When you save user and admin you can handle it. But when a user has request with jwt token, you cann't know which model will return (Admin or User)?
Use only User model for authentication and Admin model extends from User.
Redesign database like this:
users table : id, email, password, is_admin
user_details table : id, user_id, first_name, last_name, city_abbr, phone
admin_details table: id, user_id, name, phone
Put this your Admin Model for overriding all queries:
protected $table = "users";
public function newQuery()
{
return parent::newQuery()
->where("is_admin", true);
}