I'm setting up an authentication route with my API.
I am using laravel 5.5 with tymondesigns/jwt-auth 1.0.0-rc.1 and Postman to interact with the API.
The authentication route/method seems to work:
/**
* Authenticates a json request, generating a token.
*
* #param Request $request
* #return JsonResponse
*/
public function authenticate(Request $request)
{
// grab credentials from the request
$credentials = $request->only('email', 'password');
try {
// attempt to verify the credentials and create a token for the user
if (! $token = JWTAuth::attempt($credentials)) {
return response()->json(
[
'error' => 'Invalid credentials.',
'detail' => 'Please use your email and password to generate a token.'
],
401);
}
} catch (JWTException $e) {
// something went wrong whilst attempting to encode the token
return response()->json(
[
'error' => 'Could not create token',
'detail' => 'There was an internal problem and your token could not be created.'
], 500
);
}
// all good so return the token
return response()->json(compact('token'));
}
A Postman API post request returns (what seems to be) a valid response, For example:
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOi8vc29sZGVyc3RhcmFwaS5jb20ubG9jYWwvYXBpL2F1dGhlbnRpY2F0ZSIsImlhdCI6MTUwNzg4NjU2OSwiZXhwIjoxNTA3ODkwMTY5LCJuYmYiOjE1MDc4ODY1NjksImp0aSI6IkpFWjBkc0dNbEVydXRHcFciLCJzdWIiOiIwNzk2MjhDMC03QjBDLTExRTYtODRERC1DQjAzMzVGN0JBNUQiLCJwcnYiOiI4N2UwYWYxZWY5ZmQxNTgxMmZkZWM5NzE1M2ExNGUwYjA0NzU0NmFhIn0.Dl2EEaYZx3H5XXG9WUcPXYKuma0ZjCvcCsb99hgB6O4"
}
To begin with, for basic testing purposes, I am feeding this to an action using GET, with the following suffix:
?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOi8vc29sZGVyc3RhcmFwaS5jb20ubG9jYWwvYXBpL2F1dGhlbnRpY2F0ZSIsImlhdCI6MTUwNzg4NjU2OSwiZXhwIjoxNTA3ODkwMTY5LCJuYmYiOjE1MDc4ODY1NjksImp0aSI6IkpFWjBkc0dNbEVydXRHcFciLCJzdWIiOiIwNzk2MjhDMC03QjBDLTExRTYtODRERC1DQjAzMzVGN0JBNUQiLCJwcnYiOiI4N2UwYWYxZWY5ZmQxNTgxMmZkZWM5NzE1M2ExNGUwYjA0NzU0NmFhIn0.Dl2EEaYZx3H5XXG9WUcPXYKuma0ZjCvcCsb99hgB6O4
In order to test this, if I do the following:
public function globalObjects(Request $request): JsonResponse {
var_dump(JWTAuth::parseToken()->authenticate(), JWTAuth::getToken()); exit;
// ... later code that never gets reached
}
I get the following:
bool(false) object(Tymon\JWTAuth\Token)#809 (1) { ["value":"Tymon\JWTAuth\Token":private]=> string(384) "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOi8vc29sZGVyc3RhcmFwaS5jb20ubG9jYWwvYXBpL2F1dGhlbnRpY2F0ZSIsImlhdCI6MTUwNzg4NjU2OSwiZXhwIjoxNTA3ODkwMTY5LCJuYmYiOjE1MDc4ODY1NjksImp0aSI6IkpFWjBkc0dNbEVydXRHcFciLCJzdWIiOiIwNzk2MjhDMC03QjBDLTExRTYtODRERC1DQjAzMzVGN0JBNUQiLCJwcnYiOiI4N2UwYWYxZWY5ZmQxNTgxMmZkZWM5NzE1M2ExNGUwYjA0NzU0NmFhIn0.Dl2EEaYZx3H5XXG9WUcPXYKuma0ZjCvcCsb99hgB6O4" }
.. as in:
I receive the token
it does not find the user
Items of note:
my primary key is id, but it is a UUID, so a binary(16)... and thus:
I set the identifier to be:'identifier' => 'email'
Following request: here's \config\jwt.php
return [
'secret' => env('JWT_SECRET', 'AqAWUTYISA56lrl2vcRtZQn4M4zk9onl'),
'ttl' => 60,
'refresh_ttl' => 20160,
'algo' => 'HS256',
'user' => 'App\User',
'identifier' => 'email',
'required_claims' => ['iss', 'iat', 'exp', 'nbf', 'sub', 'jti'],
'blacklist_enabled' => env('JWT_BLACKLIST_ENABLED', true),
'providers' => [
'user' => 'Tymon\JWTAuth\Providers\User\EloquentUserAdapter',
'jwt' => 'Tymon\JWTAuth\Providers\JWT\Namshi',
'auth' => 'Tymon\JWTAuth\Providers\Auth\Illuminate',
'storage' => 'Tymon\JWTAuth\Providers\Storage\Illuminate',
],
];
Thanks
Here is the solution I use in my API works perfectly well.
First rewrite the class Tymon\JWTAuth\Providers\Auth\Illuminate::class
<?php
namespace Scryba\Code\Laravel\Providers\Auth\Jwt;
use Tymon\JWTAuth\Contracts\Providers\Auth;
use Illuminate\Contracts\Auth\Guard as GuardContract;
class Illuminate implements Auth
{
/**
* The authentication guard.
*
* #var \Illuminate\Contracts\Auth\Guard
*/
protected $auth;
/**
* Constructor.
*
* #param \Illuminate\Contracts\Auth\Guard $auth
*
* #return void
*/
public function __construct(GuardContract $auth)
{
$this->auth = $auth;
}
/**
* Check a user's credentials.
*
* #param array $credentials
*
* #return bool
*/
public function byCredentials(array $credentials)
{
return $this->auth->once($credentials);
}
/**
* Authenticate a user via the id.
*
* #param mixed $id
*
* #return bool
*/
public function byId($id)
{
//you can see i added hex2bin($id)because i save my UUID primary key as
//binary(16)
return $this->auth->onceUsingId(hex2bin($id));
}
/**
* Get the currently authenticated user.
*
* #return mixed
*/
public function user()
{
return $this->auth->user();
}
}
Then update \config\jwt.php file to
'providers' => [
/*
|--------------------------------------------------------------------------
| JWT Provider
|--------------------------------------------------------------------------
|
| Specify the provider that is used to create and decode the tokens.
|
*/
'jwt' => Tymon\JWTAuth\Providers\JWT\Namshi::class,
/*
|--------------------------------------------------------------------------
| Authentication Provider
|--------------------------------------------------------------------------
|
| Specify the provider that is used to authenticate users.
|
*/
//'auth' => Tymon\JWTAuth\Providers\Auth\Illuminate::class,
'auth' => Scryba\Code\Laravel\Providers\Auth\Jwt\Illuminate::class,
/*
|--------------------------------------------------------------------------
| Storage Provider
|--------------------------------------------------------------------------
|
| Specify the provider that is used to store tokens in the blacklist.
|
*/
'storage' => Tymon\JWTAuth\Providers\Storage\Illuminate::class,
],
in my routes file
////// Protected methods (requires Authenticatication)
$api->group(
[
'middleware' => ['api.auth'],
'providers' => ['jwt'],
],function($api){
$api->resource('cars', 'CarController', ['only' => [
'index' ,'show'
]]);
});
So in your case leave the source file \vendor\tymon\jwt-auth\src\JWTAuth.php as is and write your code as below in your custom class if applicable.
/**
* Authenticate a user via the id.
*
* #param mixed $id
*
* #return bool
*/
public function byId($id)
{
$id_text = $this->getPayload()->get('sub');
$uuid_helper = new UuidHelper();
$id = $uuid_helper->textIdToId($id_text);
return $this->auth->onceUsingId($id);
}
You token is received but not parsed correctly you need to check for token parsing success.
if (! $auth = JWTAuth::parseToken();) {
throw Exception('JWTAuth unable to parse token from request');
}
dd(\Auth::id());
i suggest you go to your kernal.php and add following lines in protected $routeMiddleware array
'jwt.auth' => 'Tymon\JWTAuth\Middleware\GetUserFromToken',
'jwt.refresh' => 'Tymon\JWTAuth\Middleware\RefreshToken',
Then go to your routes and use it like middleware to authenticate like this
Route::group(['middleware' => 'jwt.auth'], function () {
// Your routes here that you want to protect
Route::get('foo', function () {
return 'Hello World';
});
]):
The answer is : "this is not compatible with this feature in it's current state without extending it yourself".
I hope this helps anyone else using UUIDs as a primary key. You can't do it without editing the vendor items or extending... But, this is an easy fix.
I will be raising this with the package author and hopefully finding a more permanent solution. However, here is a workaround:
Background:
UUIDs are stored as binary(16) id columns in the database.
This is not human readable/text friendly.
To get around this, id_text fields exist as auto generated mysql fields
note - you should never search using these (it stresses the db badly)
These are text readable and thus.. can be easily used in forms etc
I created a UuidHelper to allow for easy translation
Problem in JWT:
The authentication function tries to decode an id_text as an id
obviously, this always fails
In file \vendor\tymon\jwt-auth\src\JWTAuth.php
/**
* Authenticate a user via a token.
*
* #return \Tymon\JWTAuth\Contracts\JWTSubject|false
*/
public function authenticate()
{
$id_text = $this->getPayload()->get('sub');
$uuid_helper = new UuidHelper();
$id = $uuid_helper->textIdToId($id_text);
if (! $this->auth->byId($id)) {
return false;
}
return $this->user();
}
The documentation isn't the clearest, so I assumed 'identifier' => 'email' would sidestep this problem... turns out it doesn't. I will feed this back to the authors
Modifying the core was just an exercise... I am pretty sure this class can be extended, and I will be attempting this shortly. Editing sources is of course sub-optimal most cases.
However - I hope this digging will help people understand the problem.
Related
When I deployed my Laravel application to Elasticbeanstalk and then setup the Cloudfront for it, the Validation does not work anymore. It seems like the web page redirect back to the page without error message and the input that user have just put in.
It does work on my local. Here is my code:
class CompanyRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'types' => 'required',
'email' => 'required|email',
'url' => 'nullable|url'
];
}
}
Any help is appreciate! Thank you!
I have to authenticate users via an external api (something like ldap) and have been trying to realize authentication via a closure request guard as documented here https://laravel.com/docs/master/authentication#closure-request-guards
It works fine if the user logs in correctly, however on auth failure laravel throws the mentioned error https://laravel.com/docs/master/authentication#closure-request-guards if the failed attempt is returning null from the closure (as it says in the documentation). If it just returns false, laravel doesn't throw an error, however there is no validation feedback.
Auth::viaRequest('ldap', function ($request) {
$credentials = $request->only('login_id', 'password');
if ($user = ExternalLDPAAuth::auth()) {
return $user;
} else {
return null; // laravel throws error
// return false; // <- would not throw error, but no validation
}
}
Is there an easier way to do custom authentication?
I don't really understand the documentation about https://laravel.com/docs/5.7/authentication#authenticating-users
in the end I have to write the guard just like above anyway, right?
You haven't shown the code where you're calling attempt(), but you don't need to use that method when authenticating via the request. You use attempt() when a user attempts to login with credentials and you need to explicitly attempt to authenticate. With request authentication, Laravel will automatically attempt to authenticate as the request is handled, so your code can simply check to see if auth()->check() returns true or not.
In the end I decided to customize the EloquentUserProvider instead of the guard. In the end all i needed was additional logic to validate credentials and retrieve a user by credentials in case the user hadn't logged in yet. I.e. checking the normal eloquent logic first and then checking against the external API if nothing was found (also checking for case of changed password).
class CustomUserProvider extends EloquentUserProvider
{
/**
* Validate a user against the given credentials.
*
* #param \Illuminate\Contracts\Auth\Authenticatable $user
* #param array $credentials
* #return bool
*/
public function validateCredentials(UserContract $user, array $credentials)
{
// (...)
}
/**
* Retrieve a user by the given credentials.
*
* #param array $credentials
* #return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public function retrieveByCredentials(array $credentials)
{
// (...)
}
}
// config/auth.php
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'custom',
],
// (...)
],
// providers/AuthServiceProvider.php
class AuthServiceProvider extends ServiceProvider
{
/**
* Register any authentication / authorization services.
*
* #return void
*/
public function boot()
{
$this->registerPolicies();
Auth::provider('custom', function ($app, array $config) {
return new CustomUserProvider($app['hash'], $config['model']);
});
}
}
First off, let me admit that I'm new to APIs, and right now I'm working on JWT with Laravel. I'm using tymon\jwt-auth (tymon/jwt-auth:dev-develop --prefer-source to be specific). I went through some tutorials and was able to generate JWT access token.
Here is my login code:
public function login() {
$credentials = request(['email', 'password']);
if (!$token = auth('api')->attempt($credentials)) {
return response()->json(['error' => 'Unauthorized'], 401);
}
return response()->json([
'status' => 'success',
'message' => 'Login successful',
'data' => [
'access_token' => $token,
],
]);
}
I also need to get refresh token along with the access token, and cannot find code that works in my case.
I tried adding these lines in the code:
$refresh_token = JWTAuth::refresh($token);
but the postman returns with this error:
A token is required in file
/var/www/brochill-api/vendor/tymon/jwt-auth/src/JWT.php on line 331
I can also provide other configuration snippets I used if needed. Please help!
Let's start with creating a /refresh route:
Route::post('refresh', 'AuthController#refresh');
Now, in the AuthController,
<?php
namespace App\Http\Controllers;
use Illuminate\Support\Facades\Auth;
use App\Http\Controllers\Controller;
class AuthController extends Controller
{
/**
* Create a new AuthController instance.
*
* #return void
*/
public function __construct()
{
$this->middleware('auth:api', ['except' => ['login']]);
}
/**
* Get a JWT via given credentials.
*
* #return \Illuminate\Http\JsonResponse
*/
public function login()
{
//
}
/**
* Get the authenticated User.
*
* #return \Illuminate\Http\JsonResponse
*/
public function me()
{
//
}
/**
* Log the user out (Invalidate the token).
*
* #return \Illuminate\Http\JsonResponse
*/
public function logout()
{
//
}
/**
* 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
]);
}
}
The refresh() function refreshes the access token and invalidates the current one.
For more info on these, you can checkout the official documentation of tymon/jwt-auth, which can be found here.
I changed the login function a bit, that the user can only log in with his username and his email if his email address was confirmed by a sent email.
what do I want to do
If the email address has not yet been confirmed, I would like to redirect the user to a page to confirm his email address. If the table "users, active" has a 1, the address has been confirmed.
Currently I have problems logging in with the username. Does anyone recognize a mistake?
How can I implement that? Does anyone have a similar code?
/**
* Get the needed authorization credentials from the request.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
protected function credentials(Request $request)
{
$field = filter_var($request->get($this->username()), FILTER_VALIDATE_EMAIL)
? $this->username()
: 'username';
return [
$field => $request->get($this->username()),
'password' => $request->password,
];
}
/**
* Validate the user login request.
*
* #param \Illuminate\Http\Request $request
* #return void
*/
protected function validateLogin(Request $request)
{
$this->validate($request, [
$this->username() => [
'required', 'string',
Rule::exists('users')->where(function ($query){
$query->where('active', true);
})
],
'password' => 'required|string',
], $this->validationError());
}
New loginController
class LoginController extends Controller
{
/*
|--------------------------------------------------------------------------
| Login Controller
|--------------------------------------------------------------------------
|
| This controller handles authenticating users for the application and
| redirecting them to your home screen. The controller uses a trait
| to conveniently provide its functionality to your applications.
|
*/
use AuthenticatesUsers;
/**
* Where to redirect users after login.
*
* #var string
*/
protected $redirectTo = '/iboard';
/**
* Create a new controller instance.
*
* #return void
*/
public function __construct()
{
$this->middleware('guest', ['except' => ['logout', 'userLogout']]);
}
/**
* Get the needed authorization credentials from the request.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
protected function credentials(Request $request)
{
$field = filter_var($request->get($this->username()), FILTER_VALIDATE_EMAIL)
? $this->username()
: 'username';
return [
$field => $request->get($this->username()),
'password' => $request->password,
];
}
public function login(Request $request)
{
$this->validateLogin($request);
if (Auth::once($this->credentials($request))) { //use auth once so that it will not create auth session
$user = Auth::user();
if($user->active){
Auth::login($user); //now create auth session, check
return redirect('/iboard'); //redirect to dashboard url
}else{
return redirect('email_confirm')->with('fail', 'Please confirm your email'); //redirect to email confirm page
}
}
return redirect()->back()->with('fail', "Invalid username or password");
}
public function userLogout()
{
Auth::guard('')->logout();
return view('/exit');
}
}
You can try the below code for your login, assuming you have validateLogin and credentials functions in the same controller because the below login action used both of these function. Check details here
public function login(Request $request)
{
$this->validateLogin($request);
if (Auth::once($this->credentials($request))) { //use auth once so that it will not create auth session
$user = Auth::user();
if($user->active == 1){
Auth::login($user); //now create auth session
return redirect('dashboard'); //redirect to dashboard url
}else{
return redirect('email_confirm')->with('error', 'Please confirm your email'); //redirect to email confirm page
}
}
return redirect()->back()->with('error', "Invalid username or password");
}
since a few time I started programming in Laravel for a schoolproject. Now I tried to implement a login system. My database for userinformation is running on my homestead virtual machine, running with Postgresql. I already migrated the tables, so they exist and the database is running. Problem is: When I fill in the registrationform and send it, I get an error:
FatalErrorException in RegistersUsers.php line 32: Call to a member function fails() on null
You can see the code of RegistersUsers.php underneath:
<?php
namespace Illuminate\Foundation\Auth;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
trait RegistersUsers
{
use RedirectsUsers;
/**
* Show the application registration form.
*
* #return \Illuminate\Http\Response
*/
public function getRegister()
{
return view('auth.register');
}
/**
* Handle a registration request for the application.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
public function postRegister(Request $request)
{
$validator = $this->validator($request->all());
if ($validator->fails()) {
$this->throwValidationException(
$request, $validator
);
}
Auth::login($this->create($request->all()));
return redirect($this->redirectPath());
}
}
When I try to log in it gives the warning the log in credentials doesn't exist in the database (because I haven't registered yet), so it seems that the database connection works properly.
Does someone know a solution for this?
----UPDATE----
Underneath I added my AuthController is included. You can also see my project on my github, so you can look into my other files if needed: https://github.com/RobbieBakker/LaravelProject56
class AuthController extends Controller
{
/*
|--------------------------------------------------------------------------
| Registration & Login Controller
|--------------------------------------------------------------------------
|
| This controller handles the registration of new users, as well as the
| authentication of existing users. By default, this controller uses
| a simple trait to add these behaviors. Why don't you explore it?
|
*/
use AuthenticatesAndRegistersUsers, ThrottlesLogins;
private $redirectTo = '/';
/**
* Create a new authentication controller instance.
*
* #return void
*/
public function __construct()
{
$this->middleware('guest', ['except' => 'getLogout']);
}
/**
* Get a validator for an incoming registration request.
*
* #param array $data
* #return \Illuminate\Contracts\Validation\Validator
*/
protected function validator(array $data)
{
$validator = Validator::make($data, [
'name' => 'required|max:255',
'email' => 'required|email|max:255|unique:users',
'password' => 'required|confirmed|min:6',
]);
if(!$validator->fails())
{
session(['email' => $data['email']]);
}
}
/**
* Create a new user instance after a valid registration.
*
* #param array $data
* #return User
*/
protected function create(array $data)
{
return User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => bcrypt($data['password']),
]);
}
}
I eventually solved it just by commenting the function which called this error. I'm not sure if it's supposed to be solved like that, but my login system seems to work fine now.
Thanks for your support!
You forgot return validator instance from your validator function ))
protected function validator(array $data)
{
$validator = Validator::make($data, [
'name' => 'required|max:255',
'email' => 'required|email|max:255|unique:users',
'password' => 'required|confirmed|min:6',
]);
if(!$validator->fails())
{
session(['email' => $data['email']]);
}
return $validator;
}
I had same error )
I think you need to "make" the validator.
try changing it to this..
$validator = $this->validator->make($request->all(), $rules);
see if that works...