I am using password for user Authentication, and I after every authentication I assign a secure cookie that stores the passport token. A am being able to successfully authenticate using the Auth::attempt() method, but the Auth::user() is null. Even in the same controller, on the logout() method the user is undefined and I can't even Auth::logout().
Login method:
public function login(Request $request)
{
$request->validate([
'email' => 'required|string|email',
'password' => 'required|string',
]);
$credentials = \request(['email', 'password']);
if (Auth::attempt($credentials)) {
$user = Auth::user();
$token = $user->createToken('Personal Access Token')->accessToken;
$cookie = $this->getSessionCookie($token);
return response()
->json([
'user' => $user,
'token' => $token,
], 200)
->cookie(
$cookie['name'],
$cookie['value'],
$cookie['minutes'],
$cookie['path'],
$cookie['domain'],
$cookie['secure'],
$cookie['httponly'],
$cookie['samesite']
);
} else {
return response()->json([
'error' => 'Invalid Credentials',
], 422);
}
}
Logout method:
public function logout(Request $request)
{
$request->user()->token()->each(function ($token, $key) {
$token->delete();
});
$cookie = \Cookie::forget('auth_token');
Auth::logout();
return response()->json([
'message' => 'Logged out successfully.'
], 200)->withCookie($cookie);
}
Here the Auth::logout() produces Method Illuminate\Auth\RequestGuard::logout does not exist. Otherwise the logout is successful.
My API routes:
Route::group(['prefix' => 'v1'], function() {
// Authentication
Route::post('/login', 'AuthController#login');
Route::post('/register', 'AuthController#register');
Route::post('/password/reset', 'AuthController#sendPasswordResetLink');
Route::post('/password/update', 'AuthController#callResetPassword');
// Articles
Route::get('/articles', 'ArticleController#index');
Route::middleware(['auth.header', 'auth:api'])->group(function () {
// Get Logged in User
Route::get('/user', function (Request $request) {
return $request->user(); // returns the actual logged in user
});
// Articles
Route::post('/articles', 'ArticleController#store');
Route::get('/articles/{id}', 'ArticleController#show');
Route::put('/articles/{id}', 'ArticleController#update');
Route::delete('/articles/{id}', 'ArticleController#destroy');
// Log Out
Route::post('/logout', 'AuthController#logout');
});
});
Example controller where Auth::user() is null:
class ArticleController extends Controller
{
public function index(Request $request)
{
$user = \auth()->user(); // null
$user = Auth::user(); // null
$user = $request->user(); // null
}
}
In index method I know why the user is null since the route is not wrapped in auth:api middleware, but how would I get the auth user in this method even if it's not required.
I am sure I am missing something but I don't know what. I'll be happy to provide more code.
EDIT:
My auth.api middleware:
class AuthenticationHeader
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if (!$request->bearerToken()) {
if ($request->hasCookie('auth_token')) {
$token = $request->cookie('auth_token');
$request->headers->add(['Authorization' => 'Bearer ' . $token]);
}
}
return $next($request);
}
}
API Routes
Route::group(['prefix' => 'v1'], function() {
// Authentication
Route::post('/login', 'AuthController#login');
Route::post('/register', 'AuthController#register');
Route::post('/password/reset', 'AuthController#sendPasswordResetLink');
Route::post('/password/update', 'AuthController#callResetPassword');
// Articles
Route::get('/articles', 'ArticleController#index');
Route::middleware(['auth:api'])->group(function () {
// Get Logged in User
Route::get('/user', function (Request $request) {
return $request->user(); // returns the actual logged in user
});
// Articles
Route::post('/articles', 'ArticleController#store');
Route::get('/articles/{id}', 'ArticleController#show');
Route::put('/articles/{id}', 'ArticleController#update');
Route::delete('/articles/{id}', 'ArticleController#destroy');
// Log Out
Route::post('/logout', 'AuthController#logout');
});
});
Auth Controller
<?php
namespace App\Http\Controllers\Api;
use App\Mail\ResetPassword;
use Carbon\Carbon;
use Illuminate\Http\Request;
use App\Http\Controllers\Api\BaseController as BaseController;
use App\User;
use Illuminate\Support\Facades\Auth;
use Validator;
use Illuminate\Support\Facades\Password;
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
use Illuminate\Foundation\Auth\ResetsPasswords;
//use Illuminate\Foundation\Auth\VerifiesEmails;
//use Illuminate\Auth\Events\Verified;
class AuthController extends BaseController
{
use ResetsPasswords;
/**
* Authenticate api
* #param Request
* #return \Illuminate\Http\Response
*/
public function login(Request $request)
{
$validator = Validator::make($request->all(), [
'email' => 'required|email',
'password' => 'required',
]);
if ($validator->fails()) {
return $this->sendError('Validation Error.', $validator->errors(), 400);
}
$email = $request->input('email');
$password = $request->input('password');
if (Auth::attempt(['email' => $email, 'password' => $password])) {
$user = Auth::user();
if ($user->hasVerifiedEmail()) {
$success['token'] = 'Bearer ' . $user->createToken('MyApp')->accessToken;
$success['user'] = $user->only('id', 'name', 'email', 'avatar');
return $this->sendResponse($success, 'User logged in successfully.');
} else {
return $this->sendError('Please verify your Email.', [], 400);
}
}
return $this->sendError('Wrong Credentials.', [], 400);
}
/**
* Register API
* #param Request
* #return \Illuminate\Http\Response
*/
public function register(Request $request)
{
$validator = Validator::make($request->all(), [
'name' => 'required',
'email' => 'required|email',
'password' => 'required',
'c_password' => 'required|same:password',
//'g-recaptcha-response' => 'required|captcha',
]);
if ($validator->fails()) {
return $this->sendError('Validation Error.', $validator->errors(), 400);
}
$name = $request->input('name');
$email = $request->input('email');
$password = $request->input('password');
$user = User::where('email', $email)->first();
if ($user) {
return $this->sendError('This email address is already taken. Please try another.', [], 400);
}
$user = User::create([
'name' => $name,
'email' => $email,
'password' => bcrypt($password)
]);
$user->sendApiEmailVerificationNotification();
return $this->sendResponse([], 'Please confirm yourself by clicking on verify user button sent to you on your email.');
}
/**
* Send reset password email API
* #param Request
* #return \Illuminate\Http\Response
*/
public function sendPasswordResetLink(Request $request)
{
$validator = Validator::make($request->all(), [
'email' => 'required|email',
]);
if ($validator->fails()) {
return $this->sendError('Validation Error.', $validator->errors(), 400);
}
$email = $request->input('email');
$response = Password::sendResetLink(['email' => $email], function (Message $message) {
$message->subject($this->getEmailSubject());
});
switch ($response) {
case Password::RESET_LINK_SENT:
return $this->sendResponse([], 'We have e-mailed your password reset link!');
case Password::INVALID_USER:
return $this->sendError('We can\'t find a user with that e-mail address.', [], 400);
}
}
/**
* Reset password action API
* #param Request
* #return \Illuminate\Http\Response
*/
public function callResetPassword(Request $request)
{
$validator = Validator::make($request->all(), [
'token' => 'required',
'email' => 'required|email',
'password' => 'required',
'password_confirmation' => 'required|same:password',
]);
if ($validator->fails()) {
return $this->sendError('Validation Error.', $validator->errors(), 400);
}
// Here we will attempt to reset the user's password. If it is successful we
// will update the password on an actual user model and persist it to the
// database. Otherwise we will parse the error and return the response.
$response = $this->broker()->reset(
$this->credentials($request),
function ($user, $password) {
$this->resetPassword($user, $password);
}
);
if ($response == Password::PASSWORD_RESET) {
return $this->sendResponse([], 'User password has been successfully reset.');
} else {
return $this->sendError($response, [], 400);
}
}
/**
* Logout API
* #param Request
* #return \Illuminate\Http\Response
*/
public function logout(Request $request)
{
if (Auth::check()) {
Auth::user()->oauthAcessTokens()->delete();
return $this->sendResponse([], 'User logged out successfully.');
}
}
}
Base Controller
<?php
namespace App\Http\Controllers\Api;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class BaseController extends Controller
{
/**
* success response method.
*
* #return \Illuminate\Http\Response
*/
public function sendResponse($result, $message = null)
{
$response = [
'success' => true,
'data' => $result
];
if (!empty($message)) {
$response['message'] = $message;
}
return response()->json($response, 200);
}
/**
* return error response.
*
* #return \Illuminate\Http\Response
*/
public function sendError($error, $errorMessages = [], $code = 422)
{
$response = [
'success' => false,
'message' => $error,
];
if (!empty($errorMessages)) {
$response['data'] = $errorMessages;
}
return response()->json($response, $code);
}
}
Related
So guys,
I have an app that needs to login.
After login and getting the API and token, it has to redirect to a dashboard, but unfortunately, I can't make it to a dashboard view.
I try to find answers on the forum but can't find one that suits my code.
Here is my api.php
Route::post('/login', App\Http\Controllers\api\LoginController::class)->name('login');
my web.php
Route::get('/dashboard', [Controller::class, 'dashboard']);
my LoginController
class LoginController extends Controller
{
/**
* Handle the incoming request.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
public function __invoke(Request $request)
{
//set validation
$validator = Validator::make($request->all(), [
'email' => 'required',
'password' => 'required'
]);
//if validation fails
if ($validator->fails()) {
return response()->json($validator->errors(), 422);
}
//get credentials from request
$credentials = $request->only('email', 'password');
//if auth failed
if(!$token = auth()->guard('api')->attempt($credentials)) {
return response()->json([
'success' => false,
'message' => 'Email atau Password Anda salah'
], 401);
}
//if auth success
return response()->json([
'success' => true,
'user' => auth()->guard('api')->user(),
'token' => $token
], 200);
}
my AuthController :
class AuthController extends Controller
{
public function login(Request $request){
$email = $request->input("email");
$password = $request->input("password");
$request = Request::create('http://localhost:8000/api/login', 'POST',[
'name'=>$email,
'password'=>$password,
]);
$response = json_decode(Route::dispatch($request)->getContent());
// echo($response->success);
if($response->success == 1 || true){
return redirect()->route('dashboard',["response"=>$response]);
}else{
return redirect()->back();
}
}
}
Controller.php where dashboard route is defined:
public function dashboard()
{
return view('dashboard', [
"title" => "Dashboard",
]);
}
if I'm using this code, the error I get is:
Route [dashboard] not defined.
but if I'm not using return redirect and use return view instead. I can go to my dashboard, but the URL is localhost:8000\auth\login which is not what I want.
is there any suggestion so I can get my view on Dashboard?
Thank you very much.
I made a UserController which generats an accessToken when a user registered succesfully on a page.
class UserController extends Controller
{
/**
* Login Method: in here we call Auth::attempt with the credentials the user supplied.
* If authentication is successful, we create access tokens and return them to the user.
* This access token is what the user would always send along with all API calls to have access to the APIs.
* Register Method: like the login method, we validated the user information,
* created an account for the user and generated an access token for the user.
*/
public function login()
{
$credentials = [
'email' => request('email'),
'password' => request('password')
];
if (Auth::attempt($credentials)) {
$success['token'] = Auth::user()->createToken('MyApp')->accessToken;
return response()->json(['success' => $success]);
}
$status = 401;
$response = ['error' => 'Unauthorized'];
return response()->json($response, $status);
}
public function register(Request $request)
{
$validator = Validator::make($request->all(), [
'name' => 'required',
'email' => 'required|email',
'password' => 'required',
]);
if ($validator->fails()) {
return response()->json(['error' => $validator->errors()], 401);
}
$input = $request->all();
$input['password'] = bcrypt($input['password']);
$user = User::create($input);
$success['token'] = $user->createToken('MyApp')->accessToken;
$success['name'] = $user->name;
return response()->json(['success' => $success]);
}
public function getDetails()
{
return response()->json(['success' => Auth::user()]);
}
}
My problem is that I want to remove the token when the user logs out but I dont know how to remove the access token from the user.
logout function in my UserController
public function logout()
{
Auth::user()->tokens->each(function($token, $key) {
$token->delete();
});
return response()->json([
'message' => 'Logged out successfully!',
'status_code' => 200
], 200);
}
When I test it with postman with the GET route: http://127.0.0.1:8000/api/logout. Am I missing something?
UPDATE
Here s my api.php file:
Route::resource('categories', 'App\Http\Controllers\CategoryController');
Route::post('register', 'App\Http\Controllers\UserController#register');
Route::post('login', 'App\Http\Controllers\UserController#login');
/**
* We can group the routes we need auth for
* under common middleware. It secures our routes
*/
Route::group(['middleware' => 'auth:api'], function(){
Route::get('logout', 'App\Http\Controllers\UserController#logout');
});
I am testing it in postman using the route: http://127.0.0.1:8000/api/logout and passing the Bearer token, which I get from the login request, as a value.
It should be POST Request instead of GET request, because your deleting/making change to the database.
The route should look like this:
Route::POST('logout', 'App\Http\Controllers\UserController#logout')->middleware('auth:api');
And the logout method in in UserController should be.
public function logout()
{
auth()->user()->tokens->each(function ($token, $key) {
$token->delete();
});
return response()->json([
'message' => 'Logged out successfully!',
'status_code' => 200
], 200);
}
In your logout function, it should expire the token, not delete it
public function logout(Request $request)
{
$request->user()->token()->revoke();
return response()->json([], Response::HTTP_NO_CONTENT);
}
OR if you wanna expire all his tokens:
use Illuminate\Support\Facades\Auth;
public function logout(Request $request)
{
$userTokens = Auth::user()->tokens();
foreach($userTokens as $token)
{
$token->revoke();
}
}
I'm using laravel v5.8, VueJS and passport v7.4 for Authentication.
Below is my login function:
public function login(Request $request)
{
$validator = Validator::make($request->all(), [
'email' => 'required|string|email',
'password' => 'required|string',
]);
if ($validator->fails()) {
return response([
'status' => 0,
'message' => $validator->errors()->first()
]);
}
$credentials = request(['email', 'password']);
if (!Auth::attempt($credentials))
return response()->json([
'status' => 0,
'message' => 'Unauthorized'
], 401);
$user = $request->user();
$tokenResult = $user->createToken('authToken');
$token = $tokenResult->token;
$token->save();
$user_role = Auth::user()->user_type;
$user->assignRole($user_role);
return response()->json([
'status' => 1,
'access_token' => $tokenResult->accessToken,
'token_type' => 'Bearer',
'expires_at' => Carbon::parse(
$tokenResult->token->expires_at
)->toDateTimeString(),
]);
}
My issue is my token expires in 10 seconds(this is for testing purpose). So I check for every route if the token is expired using the below function in VueJS:
isValid(token) {
const payload = this.payload(token);
if (payload) {
const datetime = Math.floor(Date.now() / 1000);
return payload.exp >= datetime ? true : false;
}
return false;
}
So this works fine, but what should i do to refresh the token?
Can we make a middleware to handle it by itself?
Or Is there anyway to detect if the user is actively using the application like
in normal session based authentication?
Looks like you are reinventing the wheel, I would recommend to make a request to passport /oauth/token which will then return the access_token and refresh token. Also to get away with a build authentication control layer I am using nuxtjs.
The below example requires guzzlehttp/guzzle package.
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Http\Resources\User;
use Illuminate\Http\Request;
use GuzzleHttp\Client;
class AuthController extends Controller
{
/**
* #param Request $request
* #return \Illuminate\Http\JsonResponse
*/
public function user(Request $request)
{
return response()->json(['user' => new User($request->user())]);
}
/**
* #param Request $request
* #return \Illuminate\Http\JsonResponse|\Psr\Http\Message\StreamInterface
*/
public function login (Request $request)
{
$http = new Client;
try {
$response = $http->post(config('app.url') . '/oauth/token', [
'form_params' => [
'grant_type' => 'password',
'client_id' => '2',
'client_secret' => 'ugjAn1BD4Cs8gAP63RqixyCOD3Z1dUrrNiEgxQtN',
'username' => $request->get('email'),
'password' => $request->get('password')
]
]);
return $response->getBody();
} catch (\GuzzleHttp\Exception\BadResponseException $e) {
if (400 === $e->getCode()) {
return response()->json(['message' => 'Invalid request. Please enter username and password'], $e->getCode());
} else if (401 === $e->getCode()) {
return response()->json([message' => 'Your credentials are incorrect. Please try again.'], $e->getCode());
}
}
return response()->json(['message' => 'Something went wrong please try again later. ' . $e->getMessage()], $e->getCode());
}
/**
* #param Request $request
* #return \Illuminate\Http\JsonResponse
*/
function logout (Request $request)
{
$request->user()->tokens->each(function ($token, $key) {
$token->delete();
});
return response()->json(['message' => 'Logged out successfully'], 200);
}
}
I have configured Jwt/tymon API authentication and use custom model, what I want is controller function should not be accessed without the token generated by JWT on login,
Route
Route::group([
'middleware' => 'api'
// 'prefix' => 'auth'
], function ($router) {
Route::post('auth/mpalogin', 'MpaLoginController#mpaLogin')->name('login');
Route::post('auth/mpalogout', 'MpaLoginController#logout');
Route::post('auth/mparefresh', 'MpaLoginController#refresh');
Route::post('auth/mpame', 'MpaLoginController#myinfo');
Route::post('auth/mpag', 'MpaLoginController#awain');
});
CONTROLLER -> The awain method at the last is accessed without sending the token, which I don't want, I want every method that I create in this controller should be accessed only by token
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Http\Requests;
use Config;
use JWTAuth;
use JWTAuthException;
use App\Mpa;
class MpaLoginController extends Controller
{
public function __construct()
{
Config::set('jwt.user', Mpa::class);
Config::set('auth.providers', ['users' => [
'driver' => 'eloquent',
'model' => Mpa::class,
]]);
$this->middleware('auth', ['except' => ['mpaLogin']]);
}
public function mpaLogin(Request $request){
$credentials = $request->only('email', 'password');
$token = null;
try {
if (!$token = auth()->attempt($credentials)) {
return response()->json([
'response' => 'error',
'message' => 'invalid_email_or_password',
]);
}
} catch (JWTAuthException $e) {
return response()->json([
'response' => 'error',
'message' => 'failed_to_create_token',
]);
}
return response()->json([
'response' => 'success',
'result' => [
'token' => $token,
'message' => 'I am front mpa',
],
]);
}
/**
* Get the authenticated User.
*
* #return \Illuminate\Http\JsonResponse
*/
public function myinfo()
{
return response()->json(auth()->user());
}
/**
* Log the user out (Invalidate the token).
*
* #return \Illuminate\Http\JsonResponse
*/
public function logout()
{
auth()->logout();
return response()->json(['message' => 'Mpa Successfully logged out']);
}
/**
* Refresh a token.
*
* #return \Illuminate\Http\JsonResponse
*/
public function refresh()
{
return $this->respondWithToken(auth()->refresh());
}
/**
* Get the token array structure.
*
* #param string $token
*
* #return \Illuminate\Http\JsonResponse
*/
protected function respondWithToken($token)
{
return response()->json([
'access_token' => $token,
'token_type' => 'bearer',
'expires_in' => auth()->factory()->getTTL() * 60
]);
}
public function awain()
{
return response()->json("xyz");
}
}
You can try this way.
Add this middleware file in your App\Http\Middleware folder.
Filename must be JWTMiddleware.php
<?php
namespace App\Http\Middleware;
use Closure;
use App\Models\Authentication\Auth;
use Tymon\JWTAuth\Facades\JWTAuth;
use Tymon\JWTAuth\Exceptions\TokenExpiredException;
use Tymon\JWTAuth\Exceptions\TokenInvalidException;
use Tymon\JWTAuth\Exceptions\JWTException;
class JWTMiddleware
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if (is_null($request->bearerToken())) {
return response()->json(['error' => 'Token required.'], 401);
}
try {
// attempt to verify the credentials and create a token for the user
$token = JWTAuth::getToken();
$apy = JWTAuth::getPayload($token)->toArray();
} catch (TokenExpiredException $e) {
return response()->json(['error' => 'Session Expired.', 'status_code' => 401], 401);
} catch (TokenInvalidException $e) {
return response()->json(['error' => 'Token invalid.', 'status_code' => 401], 401);
} catch (JWTException $e) {
return response()->json(['token_absent' => $e->getMessage()], 401);
}
return $next($request);
}
}
register this middleware in $routeMiddleware of App\Http\kernel.php file.
protected $routeMiddleware = [
....
....
'jwt' => \App\Http\Middleware\JWTMiddleware::class,
];
Add this middleware to your Route group.
Route::group(['middleware' => [ 'jwt', 'jwt.auth']], function () {
....
....
});
Working example Repo:
https://github.com/kennethtomagan/laravel-5-api-boilerplate/
Replace
$this->middleware('auth', ['except' => ['mpaLogin']]);
With this
$this->middleware('api', ['except' => ['mpaLogin']]);
Also remove the middleware from the route group. As you are adding the middleware code in the construct of the controller.
I'm wondering how to implement tymon jwt 1.0.0 rc2 with Cartalyst Sentinel 2.0 authentication package in Laravel 5.6 to take advantage of throttling and others Sentinel features.
Inside AuthController I have this login() method as mentioned in jwt-auth Docs enter link description here to validate the credentials and generate a token.
public function login()
{
$credentials = request(['email', 'password']);
if (! $token = auth()->attempt($credentials))
return response()->json(['error' => 'Unauthorized'], 401);
return $this->respondWithToken($token);
}
What I did is the following
public function login()
{
$credentials = request(['email', 'password']);
if (! Sentinel::authenticate($credentials))
return response()->json(['error' => 'Unauthorized'], 401);
$token = auth()->attempt($credentials);
return $this->respondWithToken($token);
}
But i don't think this is the right way because there is a double authentication, first by Sentinel and the second by jwt. and this is bad for performance.
And second workaround is to modify attempt() method inside JWTGuard class that resides in vendor/tymon/jwt-auth/src folder.
the default is the following
public function attempt(array $credentials = [], $login = true)
{
$this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials);
if ($this->hasValidCredentials($user, $credentials)) {
return $login ? $this->login($user) : true;
}
return false;
}
and I changed it like this
public function attempt(array $credentials = [], $login = true)
{
if ($user = Sentinel::authenticate($credentials)) {
return $this->login($user);
}
return false;
}
I don't now if this is a right solution or not ?
Just use auth('api')->user() to get user using jwt or passport with sentinel
in jwt config change with this
'auth' => Tymon\JWTAuth\Providers\Auth\Sentinel::class,
And in auth controller you can used this function
use Tymon\JWTAuth\Facades\JWTAuth;
/**
* Handle a login request to the application.
*
* #param loginRequest $request
*
* #return \Illuminate\Http\RedirectResponse|\Illuminate\Http\Response|\Illuminate\Http\JsonResponse
*/
public function login(loginRequest $request) {
$credentials = array(
'email' => $request->email,
'password' => $request->password,
);
if (! $token = JWTAuth::attempt($credentials)) {
return response()->json(['error' => 'Unauthorized'], 401);
} else {
return $this->respondWithToken($token);
}
}
/**
* Get the token array structure.
*
* #param string $token
*
* #return \Illuminate\Http\JsonResponse
*/
protected function respondWithToken($token)
{
return response()->json([
'access_token' => $token,
'token_type' => 'bearer',
'expires_in' => auth('api')->factory()->getTTL() * 60
]);
}
And now you can logged in.