As the title says CreateFreshApiToken doesnt create any cookies. So I cant use it to auth a logged in user for other requests related to the user.
I tried to set a cookie on the response and it works perfectly fine. So this has to do something with CreateFreshApiToken not working.
AuthController.php
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use App\User;
class AuthController extends Controller
{
public function signup(Request $request)
{
$request->validate([
'name' => 'required|string',
'email' => 'required|string|email|unique:users',
'password' => 'required|string'
]);
$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 signin(Request $request)
{
$request->validate([
'email' => 'required|string|email',
'password' => 'required|string'
]);
$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;
$token->save();
return response()->json([
'message' => 'Successfully signed in!'
]);
}
public function signout(Request $request)
{
$request->user()->token()->revoke();
return response()->json([
'message' => 'Successfully signed out!'
]);
}
public function user(Request $request)
{
return response()->json($request->user());
}
public function test()
{
return response()->json([
'message' => 'test'
]);
}
public function test2(Request $request)
{
return response()->json([
'laravel_token' => $request->cookie('laravel_token')
]);
}
}
Kernel.php
protected $middlewareGroups = [
'web' => [
//...
\Laravel\Passport\Http\Middleware\CreateFreshApiToken::class,
],
'api' => [
'throttle:60,1',
'bindings',
\Barryvdh\Cors\HandleCors::class,
],
];
api.php
Route::group([
'prefix' => 'auth'
], function () {
Route::post('signin', 'AuthController#signin');
Route::post('signup', 'AuthController#signup');
Route::get('test', 'AuthController#test');
Route::get('test2', 'AuthController#test2');
Route::group([
'middleware' => ['auth:api']
], function() {
Route::get('signout', 'AuthController#signout');
Route::get('user', 'AuthController#user');
});
});
And this is my angular code:
test() {
return this.http.get('http://homestead.test/api/auth/test', {withCredentials: true})
.subscribe(response => {
console.log(response);
});
}
test2() {
return this.http.get('http://homestead.test/api/auth/test2', {withCredentials: true})
.subscribe(response => {
console.log(response);
});
}
I've also setup cors with https://github.com/barryvdh/laravel-cors successfully with 'supportsCredentials' enabled. I am also sending a useless GET request to see if any laravel_token is set in the cookie but no success.
CreateFreshApiToken is part of the web middleware group, so in order for it to set cookies you need your login page to be a web route (instead of an api route).
I resolved this part of the problem by replicating CreateFreshApiToken::handler in my login controller:
$response = response()->json([], 200);
// ADDÂ THISÂ LINE:
$response->cookie(\Laravel\Passport\Passport::cookie(),$request->session()->token();
return $response;
Related
I'm currently developing a mobile apps for my web based system. I'm using laravel as my API .
Currently, I want to do where when User A login, it will only shown User A info.
How can I achieve that ?
This is my AuthController where it generate my tokens.
public function login(Request $request)
{
try {
$validateUser = Validator::make($request->all(),
[
'username' => 'required',
'password' => 'required'
]);
if($validateUser->fails()){
return response()->json([
'status' => false,
'message' => 'validation error',
'errors' => $validateUser->errors()
], 401);
}
$user = UserRegister::where('username', $request->username)->first();
if(!$user || !Hash::check($request['password'], $user->password)) {
return response([
'message' => 'Username & Password does not match'
], 401);
}
return response()->json([
'status' => true,
'message' => 'User Logged In Successfully',
'user' => $user,
'token' => $user->createToken("remember_token")->plainTextToken
], 200);
} catch (\Throwable $th) {
return response()->json([
'status' => false,
'message' => $th->getMessage()
], 500);
}
}
This is my api routes
Route::post('/login', [AuthController::class, 'login']);
// Homepage routes
Route::group(['middleware' => ['auth:sanctum']], function () {
Route::post('/logout', [AuthController::class, 'logout']);
Route::get('/technician', [HomepageController::class, 'technician']);
});
Route::middleware('auth:api')->get('/user', function (Request $request) {
return $request->user();
});
and this is my HomepageController where I tried to retrieve user A data only but it does not work.
public function technician()
{
$technician=JobRegister::where('job_assign', auth()->user()->name);
return response()->json($technician);
}
This is my query for web based system.
$query = "SELECT * FROM job_register WHERE job_assign ='{$_SESSION['username']}'"
i want to make user A can only look at their job details that been assign.
do you have any idea how to do it ? your help is much appreciated. thank you.
try this
$username=Auth()->user()->userName //this will return you username of logged in user
$query = "SELECT * FROM job_register WHERE job_assign ='$username'"
don't forget to use auth namespace top of in your controller like this
use Auth;
I have an issue with authentication in laravel web, I only what to use the JWT authentication for the api only, I notice whenever I change guard in defaults to web 'guard' => 'web' and I try to login with postman using my api it will not work and this error show("message": "Method Illuminate\Auth\SessionGuard::factory does not exist.") but the web will work, if I change it to 'guard' => 'api' I will not be able to login in the web but the api postman login will work.
Only web will work
'defaults' => [
'guard' => 'web',
'passwords' => 'users',
],
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'jwt',
'provider' => 'users',
],
],
Only api will work
'defaults' => [
'guard' => 'api',
'passwords' => 'users',
],
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'jwt',
'provider' => 'users',
],
],
Route
Route::group([
'prefix' => 'auth'
], function () {
Route::post('login', [AuthController::class, 'login']);
Route::post('logout', [AuthController::class, 'logout']);
Route::post('refresh', [AuthController::class, 'refresh']);
Route::post('me', [AuthController::class, 'me']);
});
Controller
class AuthController extends Controller
{
public function __construct()
{
$this->middleware('auth:api', ['except' => ['login']]);
}
public function login(Request $request)
{
$credentials = $request->only('email', 'password');
if ($token = $this->guard()->attempt($credentials)) {
return $this->respondWithToken($token);
}
return response()->json(['error' => 'Unauthorized'], 401);
}
public function me()
{
return response()->json($this->guard()->user());
}
public function logout()
{
$this->guard()->logout();
return response()->json(['message' => 'Successfully logged out']);
}
public function refresh()
{
return $this->respondWithToken($this->guard()->refresh());
}
protected function respondWithToken($token)
{
return response()->json([
'access_token' => $token,
'token_type' => 'bearer',
'expires_in' => $this->guard()->factory()->getTTL() * 60
]);
}
public function guard()
{
return Auth::guard();
}
}
I just want to use JWT authentication for only the api without affecting the web, Thanks
What defaults.guard config does is setting the default guard to be used if none specified.
If you want 2 different authentication methods and guards you should specify each by name in your middlewares.
So instead of Route::middleware('auth')->get(...);
you should write Route::middleware('auth:api')->get(...);
If you want to protect all routes in a group you can do it in app/Http/Kernel.php by adding this lines:
protected $middlewareGroups = [
'web' => [
...
'auth:web'
],
'api' => [
...
'auth:api'
],
];
you can use this
'defaults' => [
'guard' => 'web',
'passwords' => 'users',
],
and in controller where you need api guard use this
auth('api')
for example in login controller
public function login(Request $request)
{
$validator = Validator::make($request->all(), [
'email' => 'required|email',
'password' => 'required|string|min:6',
]);
if ($validator->fails()) {
return response()->json($validator->errors(), 422);
}
if (!$token = auth('api')->attempt($validator->validated())) {
return response()->json(['error' => 'Unauthorized'], 401);
}
$token = auth('api')->claims(['user' => auth('api')->user()])->attempt($validator->validated());
return $this->createNewToken($token);
}
i use this controller and works for me
class UserController extends Controller
{
public function __construct()
{
$this->middleware('auth:api', ['except' => ['login', 'register']]);
}
/**
* Get a JWT via given credentials.
*
* #return \Illuminate\Http\JsonResponse
*/
public function login(Request $request)
{
$validator = Validator::make($request->all(), [
'email' => 'required|email',
'password' => 'required|string|min:6',
]);
if ($validator->fails()) {
return response()->json($validator->errors(), 422);
}
if (!$token = auth('api')->attempt($validator->validated())) {
return response()->json(['error' => 'Unauthorized'], 401);
}
$token = auth('api')->claims(['user' => auth('api')->user()])->attempt($validator->validated());
return $this->createNewToken($token);
// return response()->json([
// 'token' => $this->createNewToken($token),
// ]);
}
/**
* Register a User.
*
* #return \Illuminate\Http\JsonResponse
*/
public function register(Request $request)
{
$validator = Validator::make($request->all(), [
'name' => 'required|string|between:2,100',
'email' => 'required|string|email|max:100|unique:users',
'password' => 'required|string|confirmed|min:6',
]);
if ($validator->fails()) {
return response()->json($validator->errors()->toJson(), 400);
}
$user = User::create(array_merge(
$validator->validated(),
['password' => bcrypt($request->password)]
));
return response()->json([
'message' => 'User successfully registered',
'user' => $user,
], 201);
}
/**
* Log the user out (Invalidate the token).
*
* #return \Illuminate\Http\JsonResponse
*/
public function logout()
{
Auth::logout();
return response()->json(['message' => 'User successfully signed out']);
}
/**
* Refresh a token.
*
* #return \Illuminate\Http\JsonResponse
*/
public function refresh()
{
return $this->createNewToken(auth::refresh());
}
/**
* Get the authenticated User.
*
* #return \Illuminate\Http\JsonResponse
*/
public function userProfile()
{
return response()->json(auth::user());
}
/**
* Get the token array structure.
*
* #param string $token
*
* #return \Illuminate\Http\JsonResponse
*/
protected function createNewToken($token)
{
return response()->json([
'access_token' => $token,
'token_type' => 'bearer',
'expires_in' => auth('api')->factory()->getTTL() * 60,
'user' => auth('api')->user(),
]);
}
}
and routes
Route::prefix('user')->middleware('api')->group(function () {
Route::post('/login', [UserController::class, 'login']);
Route::post('/logout', [UserController::class, 'logout']);
Route::post('/refresh', [UserController::class, 'refresh']);
Route::get('/user-profile', [UserController::class, 'userProfile']);
Route::post('/register', [UserController::class, 'register']);
});
When the user created a new account I added it's API token and returned it to the user. But I'm having trouble wanting to return the API token to the user when they view their account information.
GET /account: Returns API Token in response.
This is my code file User.php:
public function index()
{
$users = User::where('id', auth()->user()->id)->get();
return response([
'data' => UserResource::collection($users),
'message' => 'Retrieve successfully'
], 200);
}
// POST
public function store(Request $request)
{
$data = $request->all();
$validator = Validator::make($data, [
'name' => 'required|max:255|string',
'email' => 'required|email|unique:users,email',
'password' => 'required|string',
]);
if ($validator->fails()) {
return response(['error' => $validator->errors(), 'Validation Error'], 400);
}
$users = User::create($data);
$token = $users->createToken('accessToken')->plainTextToken;
return response([
'data' => new UserResource($users),
'api_token' => $token,
'message' => 'Created successfully'
], 201);
}
This is my code file api.php (route):
Route::group(['prefix' => 'v1' ], function () {
// Account
Route::post('/account', [UserController::class, 'store']);
// Protected route
Route::group(['middleware' => ['auth:sanctum']], function () {
// Account
Route::get('/account', [UserController::class, 'index']);
});
});
Use $request->bearerToken() to get bearer token.
public function index()
{
$users = User::where('id', auth()->user()->id)->get();
return response([
'data' => UserResource::collection($users),
'api_token' => $request->bearerToken(),
'message' => 'Retrieve successfully'
], 200);
}
I am using the latest version of Laravel Sanctum to create a validation method for my SPA, but I ran into an issue(notice that I dont work that long with laravel). When I login it works, but when I logout and try to login it stops working. After some testing I noticed that this was because of a cookie that was added by laravel.
// loginController
public function login(Request $request)
{
if ($this->validator($request->all())->fails()) {
//validation stuff
} else {
$credentials = [
'email' => $request->email,
'password' => $request->password,
];
$user = User::where('email', $request->email)->firstOrFail();
if (auth()->attempt($credentials)) {
$token = $user->createToken('auth')->plainTextToken;
$response_code = 200;
$response = [
'user' => $user,
'success' => true,
'errors' => false,
'message' => 'Login successfully',
'access_token' => $token,
'token_type' => 'Bearer',
];
} else {
$response_code = 422;
$response = [
'success' => false,
'errors' => [],
'message' => 'Invalid login'
];
}
}
public function logout(Request $request)
{
$request->user()->tokens()->delete();
}
I'm trying to test my login endpoint where a successful response would return the access_token among other things.
I'm using RefreshDatabase, so I changed the login method on the controller to retrieve the client_secret via a DB call. I tested with a dd() and I can confirm that the client_secret changes on each phpunit run in the terminal. The credentials are correct and the API endpoint works - just not when it's run via a test. For example, I have the passport tables set up on my mysql server and I can login successfully when running Postman. It's only when trying to run a test do I get a 401 error.
Here is my AuthTest
<?php
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;
class AuthTest extends TestCase
{
use RefreshDatabase;
/**
* #test
*/
public function a_user_receives_an_access_token()
{
\Artisan::call('passport:install');
$user = factory('App\User')->create();
$response = $this->json('POST', '/api/login', [
'username' => $user->email,
'password' => 'password'
]);
$response
->assertJson([
'access_token' => true
]);
}
}
routes/api.php
Route::post('login', 'AuthController#login');
AuthController#login:
public function login(Request $request) {
$http = new \GuzzleHttp\Client;
try {
$response = $http->post(config('services.passport.login_endpoint'), [
'form_params' => [
'grant_type' => 'password',
'client_id' => '2', //config('services.passport.client_id')
'client_secret' => DB::table('oauth_clients')->where('id', 2)->pluck('secret')[0], //config('services.passport.client_secret'),
'username' => $request->username,
'password' => $request->password
]
]);
return $response->getBody();
} catch (\GuzzleHttp\Exception\BadResponseException $e) {
if ($e->getCode() == 400 || $e->getCode() == 401) {
return response()
->json([
'status' => $e->getCode(),
'message' => 'Your email and/or password are incorrect',
'expanded' => $e->getMessage()
]);
}
return response()
->json([
'status' => $e->getCode(),
'message' => $e->getMessage()
]);
}
}
I took a look at this question and the accepted answer: How to test authentication via API with Laravel Passport?
I am unable to use the following
public function setUp() {
parent::setUp();
\Artisan::call('migrate',['-vvv' => true]);
\Artisan::call('passport:install',['-vvv' => true]);
\Artisan::call('db:seed',['-vvv' => true]);
}
This results in an error:
PHP Fatal error: Declaration of Tests\Feature\AuthTest::setUp() must be compatible with Illuminate\Foundation\Testing\TestCase::setUp()
Edit: I just added
public function setUp() :void {
parent::setUp();
\Artisan::call('migrate',['-vvv' => true]);
\Artisan::call('passport:install',['-vvv' => true]);
\Artisan::call('db:seed',['-vvv' => true]);
}
but the problem still persists
Edit again:
If I test the oauth route directly, it passes.
public function testOauthLogin() {
$oauth_client_id = 2;
$oauth_client = OAuthClient::findOrFail($oauth_client_id);
$user = factory('App\User')->create();
$body = [
'username' => $user->email,
'password' => 'password',
'client_id' => $oauth_client_id,
'client_secret' => $oauth_client->secret,
'grant_type' => 'password',
'scope' => '*'
];
$this->json('POST','/oauth/token',$body,['Accept' => 'application/json'])
->assertStatus(200)
->assertJsonStructure(['token_type','expires_in','access_token','refresh_token']);
}
But my custom endpoint that uses guzzle fails. I do not know why
Edit again:
I think the issue is with Guzzle, but I'm not sure. I found another implementation of what I'm trying to do, which is the following:
public function login(Request $request) {
$request->request->add([
'username' => $request->username,
'password' => $request->password,
'grant_type' => 'password',
'client_id' => $this->client->id,
'client_secret' => $this->client->secret,
'scope' => '*'
]);
$response = Route::dispatch(Request::create(
'oauth/token',
'POST'
));
}
The above works.