I'm trying the authenticate a specific Slim route using the BasicHttpAuth middleware in Slim Extras
This works, however it requires all routes to authenticate:
$app = new Slim();
$app->add(new HttpBasicAuth('username', 'password'));
$app->get('/', function() use ($app) {
$app->render('index.php');
});
$app->get('/admin', function() use ($app) {
$app->render('admin.php');
});
$app->run();
So how can you authenticate a single route using HttpBasicAuth?
You can do it by creating a custom middleware based on HttpBasicAuth, that only runs for a specific route:
class HttpBasicAuthCustom extends \Slim\Extras\Middleware\HttpBasicAuth {
protected $route;
public function __construct($username, $password, $realm = 'Protected Area', $route = '') {
$this->route = $route;
parent::__construct($username, $password, $realm);
}
public function call() {
if(strpos($this->app->request()->getPathInfo(), $this->route) !== false) {
parent::call();
return;
}
$this->next->call();
}
}
$app->add(new HttpBasicAuthCustom('username', 'password', 'Some Realm Name', 'someroute'));
$app->get('/someroute', function () use ($app) {
echo "Welcome!";
})->name('someroute');
Thanks to http://help.slimframework.com/discussions/questions/296-middleware-usage-only-on-specific-routes
Related
How do I make a route for /blog/* where * is a slug for a blog post?
I am making a PHP MVC Framework from scratch and I'm making trouble with the showing data in view and how to make so people can click on blog posts to view it!
Please see the github: https://github.com/fillemon10/cinemania-mvc to understand and help me.
I dont know how do deal with retrieving data from database to blog.
index.php:
<?php
use app\controllers\AuthController;
use app\controllers\BlogController;
use app\controllers\SiteController;
use app\core\Application;
use app\models\Blog;
require_once __DIR__ . '/../vendor/autoload.php';
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
$dotenv->load();
$config = [
'userClass' => \app\models\User::class,
'db' => [
'dsn' => $_ENV['DB_DSN'],
'user' => $_ENV['DB_USER'],
'password' => $_ENV['DB_PASSWORD']
]
];
$app = new Application(dirname(__DIR__), $config);
$app->on(Application::EVENT_BEFORE_REQUEST, function () {
});
$app->router->get('/', [SiteController::class, 'home']);
$app->router->get('/contact', [SiteController::class, 'contact']);
$app->router->post('/contact', [SiteController::class, 'contact']);
$app->router->get('/login', [AuthController::class, 'login']);
$app->router->post('/login', [AuthController::class, 'login']);
$app->router->get('/register', [AuthController::class, 'register']);
$app->router->post('/register', [AuthController::class, 'register']);
$app->router->get('/logout', [AuthController::class, 'logout']);
$app->router->get('/myaccount', [AuthController::class, 'myaccount']);
$app->router->get('/blog', [BlogController::class, 'blog']);
$app->run();
router.php:
<?php
namespace app\core;
use app\core\exception\NotFoundException;
class Router
{
public Request $request;
public Response $response;
protected array $routes = [];
public function __construct(Request $request, Response $response)
{
$this->request = $request;
$this->response = $response;
}
public function get($url, $callback, $params ="")
{
$this->routes['get'][$url] = $callback;
}
public function post($url, $callback)
{
$this->routes['post'][$url] = $callback;
}
public function resolve()
{
$url = $this->request->getUrl();
$method = $this->request->method();
$callback = $this->routes[$method][$url] ?? false;
if ($callback === false) {
throw new NotFoundException();
}
if (is_string($callback)) {
return Application::$app->view->renderView($callback);
}
if (is_array($callback)) {
/** #var \app\core\Controller $controller */
$controller = new $callback[0]();
Application::$app->controller = $controller;
$controller->action = $callback[1];
$callback[0] = $controller;
foreach ($controller->getMiddlewares() as $middleware) {
$middleware->execute();
}
}
return call_user_func($callback, $this->request, $this->response);
}
}
I have opted out of using Laravel's built in User Authentication due to my application's requirements. We rely on a Third Party SSO to authenticate our users, and I was unable to get Socialite to work with their SSO, so I am having to custom build a Controller to handle the authentication process. The Controller is performing b-e-a-utifully up until the part when I need to redirect the user from the Callback Route & Controller to the Member Route & Controller. It won't redirect. Period. I have tried every method I know how to redirect to another route from within the controller and it will not work.
Here is my custom AuthController for Laravel 5.3:
<?php
namespace App\Http\Controllers;
use App\User;
use Curl\Curl;
use App\Http\Controllers\PhealController as Pheal;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Facades\Auth;
use Illuminate\Routing\Redirector;
class AuthController extends Controller
{
protected $curl;
private $data;
public function __construct ()
{
$this->curl = new Curl();
$this->pheal = new Pheal();
$this->data = [];
}
public function sendToSSO()
{
$url = env('EVE_SSO_LOGIN')."?response_type=code&redirect_uri=".env('EVE_CALLBACK_URL')."&client_id=".env('EVE_CLIENT_ID')."&scope=".env('EVE_SCOPES');
return redirect($url);
}
public function handleCallback(Request $request)
{
$this->curl->setHeader('Authorization', "Basic ". base64_encode(env('EVE_CLIENT_ID').":".env('EVE_SECRET')));
$this->curl->setHeader('Content-Type', "application/x-www-form-urlencoded");
$this->curl->setHeader('Host', "login.eveonline.com");
$this->curl->post('https://login.eveonline.com/oauth/token', [
'grant_type' => 'authorization_code',
'code' => $request->code
]);
$response = $this->curl->response;
if (isset($response->error)) {
throw new \Exception($response->error_description);
}
$this->data = [
'accessToken' => $response->access_token,
'refreshToken' => $response->refresh_token
];
$this->verifyToken();
}
public function verifyToken ()
{
$this->curl->setHeader('User-Agent', "David Douglas ddouglas#douglaswebdev.net");
$this->curl->setHeader('Authorization', "Bearer ". $this->data['accessToken']);
$this->curl->setHeader('Host', "login.eveonline.com");
$this->curl->get('https://login.eveonline.com/oauth/verify');
$response = $this->curl->response;
if (isset($response->error)) {
throw new \Exception($response->error_description);
}
$this->data['characterID'] = $response->CharacterID;
$this->data['characterName'] = $response->CharacterName;
$this->data['accessTokenExpire'] = $response->ExpiresOn;
try {
$characterInfo = $this->pheal->call('eve', 'CharacterInfo', ['characterID' => $this->data['characterID']])['result'];
} catch (\Exceoption $e) {
abort(404);
}
if (!isset($characterInfo['allianceID'])) {
abort(403, "Care Factor Alliance Members Only. Sorry :-(");
}
if ($characterInfo['allianceID'] !== env('CF-ALLIANCE-ID')) {
abort(403, "Care Factor Alliance Members Only. Sorry :-(");
}
$this->data['corporationID'] = $characterInfo['corporationID'];
$this->data['corporation'] = $characterInfo['corporation'];
$user = User::find($this->data['characterID']);
if ($user) {
$this->updateUserAndLogin($user);
} else {
$this->createNewUserAndLogin();
}
}
private function getData()
{
return $this->data;
}
public function createNewUserAndLogin()
{
dd('To be Created');
}
public function updateUserAndLogin($user)
{
$user->corporationID = $this->data['corporationID'];
$user->corporation = $this->data['corporation'];
$user->accessToken = $this->data['accessToken'];
$user->refreshToken = $this->data['refreshToken'];
$user->accessTokenExpire = $this->data['accessTokenExpire'];
$user->save();
//Auth::login($user);
return redirect('member/dashboard/');
}
}
I have also tried:
return redirect()->route('member.dashboard');
With no luck.
You mean the $this->createNewUserAndLogin()? Maybe trying return $this->updateUserAndLogin($user); and return $this->verifyToken(); so you return the response on the main method of the route?
I have a middleware that authenticates a JWT user using tymon/jwt-auth package:
public function handle($request, \Closure $next)
{
if (! $token = $this->auth->setRequest($request)->getToken()) {
return $this->respond('tymon.jwt.absent', 'token_not_provided', 400);
}
try {
$user = $this->auth->authenticate($token);
} catch (TokenExpiredException $e) {
return $this->respond('tymon.jwt.expired', 'token_expired', $e->getStatusCode(), [$e]);
} catch (JWTException $e) {
return $this->respond('tymon.jwt.invalid', 'token_invalid', $e->getStatusCode(), [$e]);
}
if (! $user) {
return $this->respond('tymon.jwt.user_not_found', 'user_not_found', 404);
}
$this->events->fire('tymon.jwt.valid', $user);
return $next($request);
}
Then I have a controller and I want to pass the user from the middleware to the controller.
So I did on the controller:
public function __construct()
{
$this->user = \Auth::user();
}
The problem is that $this->user is null, but when I do this on a method of the controller, it's not null.
So:
public function __construct()
{
$this->user = \Auth::user();
}
public function index()
{
var_dump($this->user); // null
var_dump(\Auth::user()); // OK, not null
}
So the issue is that __construct is running before the middleware. How can I change that, or do you have another solution?
Update: I'm using dingo/api for routing, maybe it's an error on their side?
You should use the middleware(s) in the routes
Route::middleware('jwt.auth')->group(function() {
// your routes
});
1) Remove Your middleware from Your kernel's $middleware array
2) Put Your middleware to $routeMiddleware array with custom name jwt.auth:
protected $routeMiddleware = [
// ...
'jwt.auth' => 'App\Http\Middleware\YourAuthMiddleware'
];
2) Create BaseController in parent directory of needle controller, with function:
public function __construct() {
$this->middleware('jwt.auth');
}
3) Extend needle controller from BaseController
4) Make __construct function of needle controller to look like this:
public function __construct() {
parent::__construct();
$this->user = \Auth::user();
}
In Slim Framework v2 I use simple authentication function as a hook to check if a route needs login.
This is authentication code:
$authenticate = function ( $app ) {
return function () use ( $app ) {
if ( !isset( $_SESSION['userid'] ) ) {
$_SESSION['urlRedirect'] = $app->request()->getPathInfo();
$app->flash('danger', 'You need to login');
$app->redirect('/login');
}
};
};
this is how I use in Slim v2:
$app->get("/user/change-password", $authenticate($app), function () use ( $app ) {
$userStuff->changePassowrd($_SESSION['userid'], $newPassword, $oldPassword);
});
I can implement this to Slim v3 without a problem but I can't seem to understand how I supposed to do this with middleware(to learn and use the functionally)
I have tried this: this is my middleware class;
<?php
namespace entity;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;
class Auth
{
/**
* Example middleware invokable class
*
* #param \Psr\Http\Message\ServerRequestInterface $request PSR7 request
* #param \Psr\Http\Message\ResponseInterface $response PSR7 response
* #param callable $next Next middleware
*
* #return \Psr\Http\Message\ResponseInterface
*/
public function __invoke($request, $response, $next)
{
if ($this->authenticate()) {
$response = $next($request, $response);
} else {
echo 'You need to login';
}
return $response;
}
public function authenticate() {
if ( !isset( $_SESSION['userid'] ) ) {
return true;
}
return false;
}
}
?>
Registered it:
$app->add( new entity\Auth() );
and I don't know how to use this on a route as I did in Slim v2, where do I add this in the route to check if that route needs an authentication?
Thanks.
What you are looking for is actually excluding a route for the Auth middleware.
You do want that check for all other routes but not for changePassword.
In your __invoke you need to get the current route and only do authenticate if the route is not changePassword (if that's the name of your route).
public function __invoke($request, $response, $next)
{
$route = $request->getAttribute('route');
if (in_array($route->getName(), ['changePassword']) || $this->authenticate()) {
$response = $next($request, $response);
} else {
echo 'You need to login';
}
return $response;
}
Well the basic example is next
<?php
require 'vendor/autoload.php';
session_start();
class Auth {
public function __invoke($request, $response, $next) {
if ($this->authenticate()) {
$response = $next($request, $response);
} else {
echo 'You need to login';
}
return $response;
}
public function authenticate() {
if (isset($_SESSION['userid'])) {
return true;
}
return false;
}
}
$app = new \Slim\App();
$app->get('/open', function($request, $response, $args){
echo 'HAPPINESS FOR EVERYBODY, FREE, AND LET NO ONE BE LEFT BEHIND!';
});
$app->get('/closed', function($request, $response, $args){
echo 'You are authenticated!';
})->add(new Auth());
$app->get('/login', function($request, $response, $args){
$_SESSION['userid'] = 1;
});
$app->run();
I think there was a mistake in as authenticate function as !isset means there is no $_SESSION['userid'] and probably user is not logged in and your function tells that he is logged.
But anyway you can add middleware to single route or group of routes with add() method.
I have following routes:
// For user
Route::controller('/', 'LoginController');
//For admin
Route::group(array('prefix' => 'admin'), function() {
Route::get('/', 'admin\LoginController#index');
Route::get('/dashboard', 'admin\LoginController#show');
Route::get('/Logout','admin\LoginController#logout');
Route::resource('/setting','admin\SettingController');
});
I have user panel without prefix.
In logincontroller contain authorization codes.
I have found 'Controller method not found.' error when i open admin.but when i comment to user route then admin is working fine but user panel found same error.please help sir..thanks
Yes Here is LoginController of user
<?php
class LoginController extends BaseController {
public function getIndex()
{
if(Auth::check())
{
return Redirect::to('/user/home');
}
return View::make('login.index');
}
public function postIndex()
{
$username = Input::get('username');
$password = Input::get('password');
if (Auth::attempt(['username' => $username, 'password' => $password]))
{
return Redirect::intended('/user/home');
}
return Redirect::back()
->withInput()
->withErrors('Sorry,Username or password is incorrect');
}
public function getLogin()
{
return Redirect::to('/');
}
public function getLogout()
{
Auth::logout();
return Redirect::to('/');
}
}
Admin Login Controller
<?php
namespace admin;
class LoginController extends \BaseController {
public function showLogin() {
return \View::make('admin.login');
}
public function index()
{
return \View::make('admin.index');
}
public function store()
{
$username = \Input::get('username');
$password = md5(\Input::get('password'));
if ($mm=\DB::select('select * from admin where uname = ? and password = ?', array($username, $password)))
{
\Session::put('admin', $mm);
return \Redirect::intended('/admin/dashboard');
}
else
{
\Session::flush('admin');
return \Redirect::back()
->withInput()
->withErrors('Sorry,Unauthorized admin please try again');
}
}
public function postIndex()
{
echo 'Demo of post index';exit;
}
public function show()
{
$tt=\Session::get('admin');
return \View::make('admin.dashboard');
}
public function Logout()
{
\Session::flush('admin');
return \Redirect::to('/admin');
}
}
The problem is that Route::controller('/') is catching all requests that only have one segment. that means /admin as well. It then tries to find a getAdmin() method in the user LoginController which obviously doesn't exist.
You basically have two options here.
1. Change the route order
Routes are searched in the order you register them. If you place the admin group before the other route everything will work as expected:
Route::group(array('prefix' => 'admin'), function() {
Route::get('/', 'admin\LoginController#index');
Route::get('/dashboard', 'admin\LoginController#show');
Route::get('/Logout','admin\LoginController#logout');
Route::resource('/setting','admin\SettingController');
});
Route::controller('/', 'LoginController');
2. Make explicit routes
Instead of using Route::controller('/') you could specify each route:
Route::get('/', 'LoginController#getIndex');
Route::get('login', 'LoginController#getLogin');
// etc...
Route::group(array('prefix' => 'admin'), function() {
Route::get('/', 'admin\LoginController#index');
Route::get('/dashboard', 'admin\LoginController#show');
Route::get('/Logout','admin\LoginController#logout');
Route::resource('/setting','admin\SettingController');
});