So I'm working on an api that uses digest authentication middleware.
If a particular parameter is present in the request I want to be able to completely bypass authentication.
public function process(ServerRequestInterface $request, DelegateInterface $delegate)
{
/* TODO:: Figure out how to bypass the digest auth below */
/* Have tried: (if detect particular parameter) */
// return new Response\HtmlResponse(true);
// return new Response();
/* Begin digest authentication */
$authentication = new DigestAuthentication($this->credentials);
$authentication->realm($this->realm);
$authentication->nonce(uniqid());
return $authentication(
$request,
new Response(),
function ($request) use ($delegate) {
return $delegate->process($request);
}
);
}
Do I have the right idea here lads? Any help or suggestions welcome!
You have several options:
If the Api only has a few routes that need authentication you can manually add the middleware only for these routes, so the rest of them will not require authentication. E.g.:
'home' => [
'path' => '/',
'middleware' => [YourAuthenthicationMiddleware::class, HomePageHandler::class],
'allowed_methods' => ['GET'],
'name' => 'home',
],
If there are a few routes that don't need authentication you can put them in a path that is different than the one from the Apis and add this pipeline:
$app->pipe('/api', YourAuthenthicationMiddleware::class);
No auth path: /myApp/any/path
Auth path: /api/any/path
Set a key for each route and check it in the authentication middleware
Route:
'login' => [
'path' => '/login[/]',
'middleware' => LoginHandler::class,
'allowed_methods' => ['GET', 'POST'],
'name' => 'login',
'authentication' => [
'bypass' => true,
],
],
AuthenticationMiddleware:
$this->routeConfiguration = $config['routes'];
$routeResult = $request->getAttribute(RouteResult::class);
...
if (empty($this->routeConfiguration[$routeResult->getMatchedRouteName()]['authentication']['bypass'])) {
//try to authenticate
}
For the last option make sure that this pipe is injected:
$app->pipe(RouteMiddleware::class);
Related
I am trying to make rest api with my methods.
'rules' => [
[
'class' => 'yii\rest\UrlRule',
'controller' => ['ApiController'],
'patterns' => [
'PUT,PATCH api/{id}/update' => 'update',
'DELETE api/{id}/delete' => 'delete',
'GET,HEAD api/{id}' => 'get',
'POST api/{id}/create' => 'create',
'GET,HEAD' => 'api/index',
'{id}' => 'options',
'' => 'options',
]
],
Api controller:
/**
* Displays homepage.
*
* #return string
*/
public function actionIndex()
{
// $id = Yii::$app->request->getQueryParam("id"); //
Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
return "ok";
}
/**
* Displays homepage.
*
* #return string
*/
public function actionGet($id)
{
// $id = Yii::$app->request->getQueryParam("id"); //
Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
return "get";
}
Url api returns index action, but url api/1 doesn't return get action.
How to configure routing?
If you are ok with the default actions provided by yii you can simplify your code quite a bit to make it work.
Configure the response type on the application configuration, then you won't need to do it in each method.
Remove the 'patterns' element from your rules, yii automatically matches the patterns that you are trying to use.
Decide if you want to pluralize your rules or not, if you don't want to pluralize them you need to add 'pluralize' => false to your configuration rules.
web.config
// configure json response globally
'response' => [
'format' => Response::FORMAT_JSON,
'formatters' => [
Response::FORMAT_JSON => [
'class' => '\yii\web\JsonResponseFormatter',
'prettyPrint' => YII_DEBUG,
'encodeOptions' => JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE,
]
],
],
// Add ApiController rules
'rules' => [
[
'class' => 'yii\rest\UrlRule',
'controller' => 'api',
// uncoment the next line if you want to request '/api/2' instead of '/apis/2'
// 'pluralize' => false
],
// ... more rules here
]
ApiController
<?php
namespace app\controllers;
use yii\rest\Controller;
class ApiController extends Controller
{
public function actionIndex()
{
return 'Api index action';
}
public function actionView($id)
{
return "Get item $id";
}
}
Using the configuration provided you can request the index route sending a GET request to the /apis endpoint, to control the result customize actionIndex, you can provide a dataProvider as the response and the formatter element will deal with it correctly.
To request one element of the collection, send a GET request to the /apis/5 endpoint, where 5 is just an example $id, if you return a model, the formatter will deal with it using the fields attribute of the model.
If you want to use endpoints like in your question, i.e. without the plural form, uncomment the pluralize line on the example, the endpoints will be /api and /api/5.
There are multiple examples of this on the official documentation, the quick start and building a REST API pages make for some good reading and are packed with examples.
Personally I would recommend not naming a controller ApiController, it seems confusing, your API probably has api already on the url so you will end up with urls like https://api.mydomain.com/api/5
I'm trying to create an API with Bearer Token but I can't figure it out:
What does the route::middleware('auth:api') do
Where's the code of route::middleware('auth:api')
So, I have the following code in my Routes\Api.php file:
Route::get('/login', function (Request $request)
{
if(Auth::guard()->attempt(['email' => $request->email, 'password' => $request->password]) == FALSE)
return response()->json(['status' => FALSE]);
$user = Users::select('id', 'name', 'api_token', 'created_at')->where('email', $request->email)->firstOrFail();
return response()->json(['status' => TRUE, 'user' => $user]);
});
Route::middleware('auth:api')->get('/bookings', function (Request $request)
{
return response()->json(['its working!']);
});
I'm able to successfully connect to the route /login and retrieve the api_token. Now this token must be used in the /bookings route in order to authenticate.
I was hopping the middleware('auth:api')verify my CURL headers for the Authorization: Bearer zzzzzzzzz, but its not working.
So basically I need to understand how do I change the code logic behind auth:api or if I should create a new middleware and check for the request headers?
Diclamer
If you need custom code to handle authentication you should create your own middleware and authentication guard and use it instead of the default one that Laravel provides.
Your questions
What does the route::middleware('auth:api') do
It states that the route should implement the middleware "auth" and the middleware group "api".
Where's the code of route::middleware('auth:api')
All middleware in Laravel is defined in app/Http/Kernel.php.
In there you will probably see something like
protected $middlewareGroups = [
....,
'api' => [
'throttle:60,1',
'bindings',
],
];
and
protected $routeMiddleware = [
...,
'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
];
This means that a route using the middleware auth:api implements the api middleware group (in this case the ThrottleRequests and SubstituteBinding middleware) and the auth middleware (Authenticate).
The actual authentication guard used depends on the configuration in your auth.php config file:
'guards' => [
'api' => [
'driver' => 'token',
'provider' => 'users',
],
],
In the case above a TokenGuard is used (laravel/framework/src/Illuminate/Auth/TokenGuard.php).
So to answer your question, the code for the auth middleware can be found at
laravel/framework/src/Illuminate/Auth/Middleware/Authenticate.php
I've turned the backend into an API. And the API controllers have HttpBasicAuth type authentication.
The problem is that even after authentication in the frontend, whenever a request is made to the API, the authentication window appears.
How can I do so that when the user authenticates in the frontend, is not requested again the username and password of access when a request is made to the API?
Example a controller in API:
class CategoryController extends ActiveController
{
public $modelClass = 'api\models\Category';
public function behaviors()
{
$behaviors = parent::behaviors();
$behaviors['authenticator'] = [
'class' => CompositeAuth::className(),
'authMethods' => [
[
'class' => HttpBasicAuth::className(),
'auth' => function($username, $password) {
$out = null;
$user = \common\models\User::findByUsername($username);
if ($user != null) {
if ($user->validatePassword($password)) $out = $user;
}
return $out;
}
],
],
];
return $behaviors;
}
}
It is called sharing sessions. It also depends on if your tier apps (frontend and api) are both in the same domain. If it is, configure your frontend and api settings (<app>/frontend/config/main.php and <app>/api/config/main.php) as follow:
'components' => [
...
'request' => [
'csrfParam' => '_csrf-shared',
],
...
'user' => [
'identityClass' => 'common\models\User',
'enableAutoLogin' => true,
'identityCookie' => ['name' => '_identity-shared', 'httpOnly' => true],
],
...
'session' => [
'name' => 'advanced-shared',
],
...
It means you save cookies and session with the same name, so that when you login in frontend, and go to backend/api, the backend side fetches same cookies, therefore you'll be detected as authenticated user.
Here one important note, in order to enableAutoLogin work for both tiers, you should set same cookieValidationKey for both main-local.php settings. You can just set them manually, or edit init.php file to generate one cookieValidationKey for all tiers. (Just make sure you know what you're doing).
By the way, I think it's not a good idea to make simultaneous authentication between frontend and api. If it's frontend and backend then it's still bearable, but api interaction is different compared to frontend.
I suggest to use headers like Authorization: Bearer <token>.. You can get more information about it here Yii2 Rest Authentication
Update
I assume this is what you need.. Create a class, i.e. ApiAuth in common/components folder and paste the following code:
<?php
namespace common\components;
use yii\filters\auth\HttpBasicAuth;
class ApiAuth extends HttpBasicAuth
{
/**
* #inheritdoc
*/
public function authenticate($user, $request, $response)
{
if ($user->identity) {
return $user->identity;
}
return parent::authenticate($user, $request, $response);
}
}
This class extends from yii\filters\auth\HttpBasicAuth. Before calling a browser prompt it checks whether user->dentity is populated. If so, no promt is required.
In your Controller behavior replace HttpBasicAuth with ApiAuth class:
use common\components\ApiAuth;
...
'authMethods' => [
[
'class' => ApiAuth::className(),
'auth' => function($username, $password) {
...
As the user is already authenticated, then I just set the "AccessControl" for connected users. If they are not connected they will receive code 403 instead of 401.
This solves the problem:
class CategoryController extends ActiveController
{
public $modelClass = 'api\models\Category';
public function behaviors()
{
$behaviors = parent::behaviors();
$behaviors['access'] = [
'class' => \yii\filters\AccessControl::className(),
'rules' => [
[
'allow' => true,
'roles' => ['#'],
],
],
];
return $behaviors;
}
}
In Laravel 5.4, is there a way to send the password reset link to a separate authentication guard instead of the default one. I am using the default PasswordResetController which does the job in this way
public function company(Request $request)
{
$this->validate(request(), [
'email' => 'required|email',
]);
$response = Password::sendResetLink([
'email' => $request->email
]);
//overridden if condition
if($response == "passwords.sent")
{
return back()->with('message','Password reset link has been sent, please check your email');
}
return back()->with('message', 'No such email address in our records, try again');
}
The sendResetLink() method checks and sends the reset link to the default guard, but I am defining a new guard in auth.php called web
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'companies',
],
sendResetLink method is like this
public function sendResetLink(array $credentials)
{
// First we will check to see if we found a user at the given credentials and
// if we did not we will redirect back to this current URI with a piece of
// "flash" data in the session to indicate to the developers the errors.
$user = $this->getUser($credentials);
if (is_null($user)) {
return static::INVALID_USER;
}
Any way for this method to check in a separate table or use a separate auth guard?
Here is my method of how to send password reset link to a guard which is a part of multi-auth system.
I am going to assume you have properly set your new guard in config/auth.php
which may look like below code:
I use the word admin for the new guard's name for better understanding.
'guards' => [
'admin' => [
'driver' => 'session',
'provider' => 'admins',
],
]
'providers' => [
'admins' => [
'driver' => 'eloquent',
'model' => App\Admin::class,
],
]
'passwords' => [
'admins' => [
'provider' => 'admins',
'table' => 'password_resets',
'expire' => 15,
],
]
You will have to create new controllers(AdminForgotPasswordController and AdminResetPasswordController) for your new guard
They both use the Password facade and AdminResetPasswordController uses Auth facade as well.
Modify the construct function according to your new guard.
So add this to both controllers, because we have specific type of guest users.
public function __construct()
{
$this->middleware('guest:admin');
}
Now we need to tell AdminResetPasswordController to use the proper guard for authenticating.
So add this method to the controller
protected function guard()
{
return Auth::guard('admin');
}
Now add this piece of code to both controllers.
protected function broker()
{
return Password::broker('admins'); //set password broker name according to guard which you have set in config/auth.php
}
Notice: It's not the only step for implementing password reset for guard you will have to take other steps like creating new routes, notifications, forms and corresponding views.
In your auth.php configuration file, you may configure multiple "guards", which may be used to define authentication behavior for multiple user tables. You can customize the included ResetPasswordController to use the guard of your choice by overriding the guard method on the controller. This method should return a guard instance:
protected function guard()
{
return Auth::guard('guard-name');
}
The magic lies, in using the broker - the PasswordBroker for your custom guard. But, you make sure to set up multiple password brokers in your auth.php configuration file.
protected function broker()
{
return Password::broker('name');
}
One simple way could be
$response = Password::broker('guard-name')->sendResetLink([
'email' => $request->email
]);
also don't miss reset function
$response = Password::broker('guard-name')->reset([
'email' => $request->email
],function(){// save changes})
I am trying to use JWT for laravel web page instead of session. so I made some changes.
Installed jwt-auth and configure
Then changed default guard as api in config/auth.php
'defaults' => [
'guard' => 'api',
'passwords' => 'users',
],
'guards' => [
...
'api' => [
'driver' => 'token',
'provider' => 'users',
],
],
Now I am getting error
(1/1) FatalErrorException Call to undefined method
Illuminate\Auth\TokenGuard::attempt() in AuthenticatesUsers.php (line
75)
How to fix this and start token authentication for laravel web page(blades not API).
I'm also using jwt protecting our api. You should change your config like below:
'defaults' => [
'guard' => 'api',
'passwords' => 'users',
],
'guards' => [
...
'api' => [
'driver' => 'jwt', // KEY POINT!!!
'provider' => 'users',
],
],
Make sure the jwt library installed correctly:
Tymon\JWTAuth\Providers\LaravelServiceProvider::class is added in your config/app.php.
Your user model implements JWTSubject interface if you use eloquent model in your provider.
I found the solution here : https://github.com/tymondesigns/jwt-auth/issues/860
In /routes/api.php - added a few basic authentication routes
Route::post('login', 'Auth\LoginController#login');
Route::get('/user', function (Request $request) {
$user = $request->user();
return dd($user);
})->middleware('auth:api');
In /app/http/Controller/auth/LoginController.php
and then override methods in login contoller
public function login(Request $request)
{
$credentials = $request->only(["email","password"]);
if ($token = $this->guard()->attempt($credentials)) {
return $this->sendLoginResponse($request, $token);
}
$this->incrementLoginAttempts($request);
return $this->sendFailedLoginResponse($request);
}
protected function sendLoginResponse(Request $request, $token)
{
$this->clearLoginAttempts($request);
return $this->authenticated($request, $this->guard()->user(), $token);
}
protected function authenticated(Request $request, $user, $token)
{
setcookie("jwt_token", $token);
return redirect('/');
return response()->json([
'token' => $token,
]);
}
protected function sendFailedLoginResponse(Request $request)
{
return response()->json([
'message' => "not found",
], 401);
}
Adding middleware AddToken
public function handle($request, Closure $next)
{
$token = isset($_COOKIE["jwt_token"])?$_COOKIE["jwt_token"]:"";
//$request['token'] = $token;//this is working
$request->headers->set("Authorization", "Bearer $token");//this is working
$response = $next($request);
//$response->header('header name', 'header value');
return $response;
}
Register middleware in Kernel.php
protected $middleware = [
....
\App\Http\Middleware\AddToken::class,
];
I think you can try this :
'defaults' => [
'guard' => 'web',
'passwords' => 'users',
],
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'token',
'provider' => 'users',
],
EDIT
You can find some help from the step by step example. In this example you need to focus on how to configure and use that token base authentication.
Hope this help you well.
Please refer this link. If you are using api as default then laravel authentication will throw an error.
Laravel uses default Session based authentication out of the box with the default scaffolding users-view-controller that you already have. You have additional means of adding your own custom guard in the doc, so you can make use of the guard as needed.
Therefore as #KevinPatel suggested, revert back to the default configuration, then in your route: group the route you want to be under JWT authentication, add the JWTMiddleware, in this case you have to update the controller responsible for your authentication to use the JWTAuth instead of the default auth.
You should check this answer if you need to understand it better check this answer on Laracasts
One recommended way to incorporate the JWTAuth is to go for Dingo API (of course you are not building api, but) because Dingo already added some flesh to the authentication and other routes management - so things are pretty easy to use and configure