I have a question regarding Authentication in Laravel 5.x. I’ve been specifically looking at tymondesigns/jwt-auth and irazasyed/jwt-auth-guard packages to do the JSON web token authentication token handling in my Laravel application.
I am not using a local database whatsoever, nor do I want to. I have environment variables set up in .env for my API’s URL, USERNAME & PASSWORD. The Guzzle PHP HTTP client is doing the trick just fine, connecting and returning data between the API and my application as needed.
However, I need to set up Authentication within my Laravel instance. I run into problems here, and the auth is wanting a DB connection.
$token = JWTAuth::attempt($credentials)
Here's the exception:
PDOException in Connector.php line 55:
SQLSTATE[HY000] [14] unable to open database file
How can I make use of JWT without using a database?
How can I COMPLETELY shut-off database connections within Laravel?
Thanks.
UPDATE:
Using tymon/jwt-auth, I've set things up within the routes, Kernel, Middleware, etc.
I created a "claim" successfully, but I need to create the token by encoding the "payload."
$this->username = $request->username;
$sub = $this->username;
$iat = time();
$jti = md5($sub . $iat);
$aud = env('APP_URL');
$this->claims = [
'sub' => $sub,
'iat' => $iat,
'exp' => time() + (2 * 7 * 24 * 60 * 60),
'nbf' => $iat,
'iss' => 'khill',
'jti' => $jti,
'aud' => $aud,
];
$payload = JWTFactory::make($this->claims);
How do I get the custom token?
You should define a custom Authentication Provider and set it in config/jwt.php.
Example of provider
Put this class anywhere you like.
namespace MyNamespace;
use Tymon\JWTAuth\Providers\Auth\AuthInterface;
class MyCustomAuthenticationProvider implements AuthInterface
{
public function byCredentials(array $credentials = [])
{
return $credentials['username'] == env('USERNAME') && $credentials['password'] == env('PASSWORD');
}
public function byId($id)
{
// maybe throw an expection?
}
public function user()
{
// you will have to implement this maybe.
}
}
Example of configuration
In the providers array in config/jwt.php, change this:
'auth' => 'Tymon\JWTAuth\Providers\Auth\IlluminateAuthAdapter',
to this:
'auth' => 'MyNamespace\MyCustomAuthenticationProvider',
Other considerations
Using the env() function anywhere is not good practice. It's better to use it in your config files, and then use the config() function anywhere else.
You may need to reimplement also the User Provider.
JWTAuth::attempt() won't help you with this, because it hits the database for you behind the scenes. You need some other way to check the environment credentials.
Add a custom method to a class somewhere which will do that for you or pass the credentials against the API you are hitting with Guzzle.
Code example:
public function authenticate($username, $password)
{
if(!$username === env('USERNAME') or !$password === env('PASSWORD')) {
// return a message that the user could not be authenticated or false.
}
// Generate the JWT token here and store it somewhere.
}
As a quick fix I decided to implement the following custom code...
1) Created custom middleware to handle the logic.
class CustomMiddleware
{
protected $loginPath = 'login';
public function handle($request, Closure $next) {
$logged_in = $request->session()->get('logged_in');
if (!$logged_in) {
return redirect()->guest('login')->with('flag','1');
}
return $next($request);
}
}
2) Added a reference to the middleware class.
class Kernel extends HttpKernel
{
protected $routeMiddleware = [
'custom' => \App\Http\Middleware\CustomMiddleware::class,
];
}
3) Added it to routes.php.
Route::group(['middleware' => ['custom']], function () {
// Add routes here
}
yes.
you can create jwt token without database using tymondesigns/jwt-auth package...
for that you have to use jwt::encode method...
let me explain ...
first you have to put your credential in .env file...
then i am recomending you to use custom claims ...
after that you can create jwt token using below code ...
$customClaims = ['foo' => 'bar', 'baz' => 'bob'];
$factory = JWTFactory::customClaims($customClaims);
$token = JWTAuth::encode($payload);
for further details you can refer below link
wiki
Related
Is there any way that I can allow user to login only from one device?
Thanks in advance
Well, you would need to check at a central place, if there is an already existing session for the user that currently want to log in - and if yes, delete all existing sessions.
The central place would proably be when the login happens or inside an auth middleware.
To delete all existing sessions for the user you can run
DB::table('sessions')->where('user_id', $user->id)->delete();
Log in only from one device, f. ex. Laptop
That is probably not possible as each device would need to send a unique identifier - which it doesn't. As example, your Laptop would need to send a unique identifier to the Laravel system, so that your Laravel application would know, that it is the Laptop the login is coming from.
The login forms normally only takes a username/email and a password, so no unique property to identify your Laptop.
You could probably check for browser user agent or things like this, but that is all fakeable and does not guarantee a 100% proof identification of the device.
You can use deviceInspect middleware and check user agent (it could be fake as #codedge said) and use it after auth middleware
As you can see the user will be authenticated but routes will be protected by device
Create middleware
class DeviceInspect
{
public function handle($request, Closure $next)
{
$user = Auth::user(); //or $request->user()
// TODO get enabled device/s from datebase for $user - by userId
$enabledDevice = "Dalvik/2.2.0 (Linux; U; Android 10.0.1; AM-A89R Build/NMB55D)"; //example
$currentDevice = $request->userAgent(); //or $_SERVER['HTTP_USER_AGENT'];
//it could be fake like codedge said
if ($enabledDevice !== $currentDevice) {
$data = array(
"device" => false,
"message" => "your message to user",
);
return response([$data], 401); // or something else
}
return $next($request);
}
}
add this to App\Http\Kernel
protected $routeMiddleware = [
...
'device' => 'App\Http\Middleware\DeviceInspect',
];
and use it like below
//in controller
class SomeController extends Controller {
public function __construct() {
parent::__construct();
$this->middleware(['auth', "device"]);
}
}
or
//Or in routes
Route::get('/profil', function () {
//
})->middleware(['auth', 'device']);
or
Route::group(['prefix' => '/v1/data', 'namespace' => 'Api\V1', 'as' => 'api.', 'middleware' => ['auth:api', 'device']], function () {
Route::resource('activity', 'Data\DataController', ['only' => ['index', 'show']]);
});
I have installed CakePhp 3.8 and i need use JWT authentication.
I have tryed installing and configuring CakePHP/Authentication (https://book.cakephp.org/authentication/1.1/en/index.html) but can not configure this.
My configuration:
PHP 7.2.19
MySQL 5.7.27
Apache 2.4.29
CakePHP 3.8
Authentication Plugin 1.1
firebase/php-jwt
I followed the guide configurations, and have add at AppController.php
// /src/Controller/Appcontroller.php
public function initialize()
{
parent::initialize();
$this->loadComponent('Authentication.Authentication', [
'logoutRedirect' => '/administrators/login' // Default is false
]);
....
In Application.php
// /src/application.php
class Application extends BaseApplication implements AuthenticationServiceProviderInterface
....
public function getAuthenticationService(ServerRequestInterface $request, ResponseInterface $response)
{
$service = new AuthenticationService();
$service->loadIdentifier('Authentication.JwtSubject');
$service->loadAuthenticator('Authentication.Jwt', [
'returnPayload' => false
]);
return $service;
}
....
public function middleware($middlewareQueue)
{
....
// Add the authentication middleware
$authentication = new AuthenticationMiddleware($this);
// Add the middleware to the middleware queue
$middlewareQueue->add($authentication);
return $middlewareQueue;
}
How i can login for first time and retrive JWT token?
-------------------EDIT-------------------
Thankyou, your solutions workly perfectly.
But now i have CORS problem with Angular FE GET request, befor GET this try one OPTIONS request whit CORS error.
I have this CORS policy in my AppController
// Accepted all CORS
$this->response = $this->response->cors($this->request)
->allowOrigin(['*'])
->allowMethods(['GET','POST','PUT','DELETE','OPTIONS','PATCH']) // edit this with more method
->allowHeaders(['X-CSRF-Token']) //csrf protection for cors
->allowCredentials()
->exposeHeaders(['Link'])
->maxAge(60)
->build();
You'd have to handle that on your own, ie create an endpoint that handles login requests, and upon successful authentication creates a JWT token containing the required identifier.
For username/password authentication for example you can use the Form authenticator and the Password identifier:
$service->loadIdentifier('Authentication.Password');
$service->loadIdentifier('Authentication.JwtSubject');
$service->loadAuthenticator('Authentication.Form', [
'loginUrl' => '/users/login'
]);
$service->loadAuthenticator('Authentication.Jwt', [
'returnPayload' => false
]);
With that example in UsersController create a login() action like this (that's just a very basic, hopefully self-explanatory example), check the authentication status, if valid generate a token, if invalid generate an error:
public function login()
{
if ($this->Authentication->getResult()->isValid()) {
$userId = $this->Authentication->getIdentityData('id');
$token = \Firebase\JWT\JWT::encode(
['sub' => $userId],
\Cake\Utility\Security::getSalt()
);
$status = 200;
$response = [
'token' => $token,
];
} else {
$status = 403;
$response = [
'error' => 'Authentication required',
];
}
return $this
->getResponse()
->withStatus($status)
->withType('json')
->withStringBody(json_encode($response));
}
It probably wouldn't hurt if the cookbook would have a complete example for token authentication.
See also
Authentication Cookbook > Migration from the AuthComponent > Login action
Authentication Cookbook > Quick Start > Checking the login status
Authentication Cookbook > Quick Start > Using Stateless Authenticators with other Authenticators
I want to test login method inside the controller, because I am a beginner in writing the tests and because I think that it is wise to test login method. if anyone has any objections let me know. Also I have found many solutions to test the login, but in cakephp 2.
/**
* Log the user into the application
*
* #return void|\Cake\Network\Response
*/
public function login()
{
if ($this->request->is("post")) {
$user = $this->Auth->identify();
if ($user) {
$user = $this->addUsersAssociated($user);
$user = $this->addUserDetailsToSession($user);
$this->Auth->setUser($user);
return $this->redirect($this->Auth->redirectUrl());
}
// User not identified
$this->Flash->error(__d('cockpit', 'Your username or password is incorrect'));
}
}
For example I want to test it when someone comes with wrong and right username/password.
I am a total beginner and I would also like if someone can point me into the right direction (where is the quickest way to learn how to do this). I would like from someone who is independent to learn how to test my code. In other words I don't want to go to official documentation. (Have been there already)
Figure out what happens, respectively what should happen in the different situations, and create tests with proper expectations.
Like on successful login, the user data is being set in the auth storage and a redirect header is being set, that's something you could test. Likewise on a non-successful login attempt, no user data is stored, no redirect header is being set, and a flash message is being rendered.
All these things can easily be checked in a controller integration test using either helper assertion methods, or even manually via the provided session and response objects, check:
$_requestSession
$_response
assertSession()
assertRedirect()
assertRedirectContains()
assertResponse()
assertResponseContains()
etc...
Here's two very basic examples:
namespace App\Test\TestCase\Controller;
use Cake\TestSuite\IntegrationTestCase;
class AccountControllerTest extends IntegrationTestCase
{
public function testLoginOk()
{
$this->enableCsrfToken();
$this->enableSecurityToken();
$this->post('/account/login', [
'username' => 'the-username',
'password' => 'the-password'
]);
$expected = [
'id' => 1,
'username' => 'the-username'
];
$this->assertSession($expected, 'Auth.User');
$expected = [
'controller' => 'Dashboard',
'action' => 'index'
];
$this->assertRedirect($expected);
}
public function testLoginFailure()
{
$this->enableCsrfToken();
$this->enableSecurityToken();
$this->post('/account/login', [
'username' => 'wrong-username',
'password' => 'wrong-password'
]);
$this->assertNull($this->_requestSession->read('Auth.User'));
$this->assertNoRedirect();
$expected = __d('cockpit', 'Your username or password is incorrect');
$this->assertResponseContains($expected);
}
}
See also
Cookbook > Testing > Controller Integration Testing
Cookbook > Testing > Controller Integration Testing > Testing Actions That Require Authentication
Cookbook > Testing > Controller Integration Testing > Assertion methods
I have a custom need where I am trying to connect Laravel with Django app. Currently, I am not using laravel's default login post method to establish user session, instead of that I am trying to access Auth::attempt($credentials);. By this way, I am able to establish user session in my custom login controller whereas in other controllers the session is not established.
Login controller:
$credentials = array('email' => $userjson["email"],'password' => $password);
Auth::attempt($credentials);
if(Auth::guest())
echo "guest";
else
return redirect()->intended('/dashboard');
Result: redirect to the dashboard page (which means session is established)
Dashboard controller
if(Auth::check())
echo "true";
else
echo "false";
Result: false (which means the session is not established)
Can someone help me to resolve this?
Use this code .we'll need to make sure to import the Auth facade at the top of the class .For more help go to https://laravel.com/docs/5.2/authentication and ready topic Manually Authenticating Users. Thanks
namespace App\Http\Controllers;
use Auth;
class OtherController extends Controller
{
/**
* Handle an authentication attempt.
*
* #return Response
*/
public function authenticate()
{
if (Auth::attempt(['email' => $email, 'password' => $password])) {
// Authentication passed...
return redirect()->intended('dashboard');
}
}
}
I have a problem with POST cURL request to my application.
Currently, I'm building RESTFUL registration function using laravel 5.
The routes for this is example is
localhost:8000/user/create
I pass value using cURL function on terminal
curl -d 'fname=randy&lname=tan&id_location=1&email=randy#randytan.me&password=randytan&remember_token=Y&created_at=2015-03-03' localhost:8000/auth/register/
And this is my routes.php
Route::post('user/create', 'UserController#create');
And this is my function to store the registration user
public function create()
{
//function to create user.
$userAccounts = new User;
$userAccounts->fname = Request::get('fname');
$userAccounts->lname = Request::get('lname');
$userAccounts->id_location = Request::get('id_location');
$userAccounts->email = Request::get('email');
$userAccounts->password = Hash::make(Request::get('password'));
$userAccounts->created_at = Request::get('created_at');
$userAccounts->save();
return Response::json(array(
'error' => false,
'user' => $userAccounts->fname . " " . $userAccounts->lname
), 200);
}
Executing the cURL syntax above, I'm getting this error TokenMismatchException
Do you have any ideas?
Because I'm implementing middleware only in my few urls, and this cURL registration url is not tight into any authentication mechanism.
Thanks before.
In Laravel 5 (latest version) you can specify routes you want to exclude in /app/Http/Middleware/VerifyCsrfToken.php
class VerifyCsrfToken extends BaseVerifier
{
/**
* The URIs that should be excluded from CSRF verification.
*
* #var array
*/
protected $except = [
'rest-api/*', # all routes to rest-api will be excluded.
];
}
Hope this helps.
Laravel 5 enforces CSFR token authentication in middleware by default.
you can disable CSFR on selected route Here is the link
or you can try some of these solutions. Hope so it will help.
changing your csfr token method /app/Http/Middleware/VerifyCsrfToken.php
public function handle ($request, Closure $next)
{
if ( !$request->is("api/*"))
{
return parent::handle($request, $next);
}
return $next($request);
}
In my case, i needed to add the route on api.php instead of web.php