laravel modify headers before authenticating (token based / api) - php

I have an api that used Dingo API. The current users therefore use a
X-Api-Key header for authentication. I now want to switch to laravels implemented API system which requires an Authorization header.
Is it possible to tell laravel which header to use.
Or preferrably:
Is there a hook that I can use to modify the headers (e.g. copy the X-Api-Key header value into the Authorization header), before authentication takes place?

You can create middleware like this:
<?php
namespace App\Http\Middleware;
use Closure;
class ModifyHeader extends BaseAuthorize
{
public function handle($request, Closure $next)
{
if ($authorization = $request->header('X-Api-Key')) {
$request->headers->set('Authorization', $authorization);
}
return $next($request);
}
}
Then you need to add this middleware to $middlewareGroups or $routeMiddleware for example like this:
protected $middlewareGroups = [
'api' => [
// ...
\App\Http\Middleware\ModifyHeader::class,
],
// ...
];
and then you should make sure that routes you want to make the change are in api middleware. Of course you can also create custom middleware group for this or apply this for selected routes.
Then for example if you add such route:
Route::group(['middleware' => 'api'], function () {
Route::get('/test', function () {
dd(request()->header('Authorization'));
});
});
You should get the same value that is passed in X-Api-Key header.

Related

Laravel API token authentication vs web Route::prefix('api')

I'm wondering what is a better practice using the API with a token or the web router with a prefix:
Have a token that changes every time a user logs in. API:
Route::get('userdata/{key}', 'userController#show');
vs
Route::prefix('api')->group(function () {
Route::get('userdata', 'userController#show');
});
In the web router I can use cookies to verify an user.
Which of these is better?
You don`t need to use any 'key' or something fields in route/body to get data for logged / session user. You can create static route for get 'self-data' and fetch current user data according header (bearer or something other auth) or cookies.
Route::get('userdata', 'userController#show');
with cookies/header will be ok. Also don`t forget to use auth middleware, where you should validate cookies/header.
Group all the api routes and apply your custom middleware that will check for the bearer token in request headers. Here is how to do it:
Create a middleware:
class AuthenticateWithToken
{
public function handle($request, Closure $next)
{
$token = $request->bearerToken();
if ($token==='valid') {
return $next($request);
}
throw new Exception();
}
}
Register your middleware in App\Http\Kernel.php:
class Kernel extends HttpKernel
{
protected $routeMiddleware = [
'authenticateWithToken' => AuthenticateWithToken::class
];
}
Apply middleware on routes:
Route::middleware('authenticateWithToken')
->prefix('api')
->group(function () {
Route::get('/route1', 'YourController#action1');
Route::get('/route2', 'YourController#action2');
});

How to change Laravel's default broadcast auth middleware

So, as my title says, I want to change Laravel's default Broadcast auth middleware to a custom auth middleware that I made which uses token-based authentication. I made this because my app is an API-based app, and, when a user authenticates, I create a session token and send it to him and also store it inside the DB with an expires_at column.
I am using Pusher.
I have the following middleware:
class AuthCustom
{
public function handle($request, Closure $next)
{
// if we have the session token stored in header
if ($request->header('x-session')) {
$session = Session::where('id', $request->header('x-session'))->where('expires_on', '>=', date('Y-m-d G:i:s'))->with('user')->first();
if ($session !== null) {
$user = (new User())->where('id', $session->user_id)->first();
if ($user !== null) {
$request->merge(['user' => $user]);
return $next($request);
}
}
}
}
My BroadcastServiceProvider code is as follows:
class BroadcastServiceProvider extends ServiceProvider
{
public function boot()
{
Broadcast::routes();
require base_path('routes/channels.php');
}
}
If I put Broadcast::routes(['middleware' => 'authcustom']); in BroadcastServiceProvider, the boradcasting/auth gives a 403 status code because $request->user() is null, which then results in an Access forbidden.
I have tried searching the whole damn web, and I found nothing about changing the default auth middleware for broadcasting.
I even tried removing Broadcast::routes() and customizing a new route /broadcast which returned a Pusher socket_auth object and everytime I got a 419 Unkown status code.
Any ideas or maybe you can point me in the direction where I could manage this?
Thank you!
Later edit:
My JS Echo connection looks like this:
Vue.use(VueEcho, {
broadcaster: 'pusher',
key: 'xxxxxxxxxxxxxx',
cluster: 'eu',
authEndpoint: 'http://localhost/api.easycargo.ro/public/broadcasting/auth',
auth: {
headers: {
'x-session': this.auth.token
}
}
});
I'm glad you got something working. For later readers, here's a more Laravel-esque way to solve the problem in the question: create a custom auth guard used to authenticate requests for the special routes.
Laravel's AuthManager includes a helper method—viaRequest()—that simplifies the creation of a Guard that authenticates a user with data from the request context without the need to fully-implement Illuminate\Contracts\Auth\Guard. We can bind our custom guard in the boot() method in AuthServiceProvider.php:
public function boot()
{
Auth::viaRequest('custom-auth', function ($request) {
// Any custom user-lookup logic here. For example:
if ($request->header('x-session')) {
$user = // ...try to fetch a user...
return $user;
}
});
}
As we can see, we just pass a closure to the viaRequest() method that returns a User object when authentication succeeds, or null when authentication fails.
Next, we'll tell Laravel about our new auth guard by adding an entry to the 'guards' array in config/auth.php:
'guards' => [
...
'broadcasting' => [
'driver' => 'custom-auth',
],
],
Finally, we need to update the middleware for any routes that should authenticate a user with our custom Guard. We can use Laravel's built-in auth middleware and specify which guard to apply as a middleware parameter. For example, we'll initialize the broadcasting routes in the question's BroadcastServiceProvider.php:
Broadcast::routes([ 'middleware' => [ 'auth:broadcasting', ... ] ]);
...where broadcasting matches the name we assigned to our custom Guard in config/auth.php.
This approach enables us to use all of Laravel's Auth services, provides a more central place to define our authentication logic, and simplifes automated testing because we can more easily mock up authentication as needed.
I actually managed to find a solution, so all I needed to do was bind the $user that I got in my custom auth middleware to the request by doing the following thing:
$request->merge(['user' => $user]);
//add this
$request->setUserResolver(function () use ($user) {
return $user;
});
and now $request->user() which laravel checks returns the user object and passes the validation.

Laravel API controller returns 404 when hit through Twilio

I am trying to set up a route that I can let Twilio hit, that will return a response from Laravel.
But every Twilio request kicks back a 404 Http Response.
Feels like I've got something misconfigured.
I have
Created ControllerClass SmSController with a reply method.
Added my route to routes/api.php to hit the controller.
Added my URL to the $except property in app/Http/Middleware/VerifyCsrfToken.php
Here's my app/Http/Controllers/SmsController.php:
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Twilio\Twiml;
class SmsController extends Controller
{
public function reply(Request $request) {
$response = new Twiml;
$response->message("Hello World!");
print $response;
}
}
And Here's my routes/api.php:
Route::group([
'middleware' => 'auth:api'
], function () {
//
});
Route::post('sms/reply', 'SmsController#reply');
And inside the class VerifyCsrfToken I have:
protected $except = [
'sms/reply'
];
Edit: I should mention, I have Twilio to hit the URL: https://www.MYWEBSITE.com/sms/reply, which I think should correspond to the route I set up in api.pip
All the routes inside of api.php are prefixed with api inside a url, so your sms/reply is actually api/sms/reply, does this make sense?

Using session in custom middleware in laravel

I've simple middleware which checks if there is a key in user session.
<?php
namespace App\Http\Middleware;
use Closure;
class CustomAuth
{
public function handle($request, Closure $next)
{
if($request->session()->has('uid')){
return $next($request);
}
else{
return view('unauth');
}
}
}
The problem is that I always get "Session store not set on request." error. Here is my route:
Route::get('home', function () {
return view('home');
})->middleware('web', 'CustomAuth');
I've added the middleware in app\Http\Kernel.php in the variable $middleware
protected $middleware = [
\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
\App\Http\Middleware\CustomAuth::class
];
I also tried changing my route to this:
Route::group(['middleware' => ['web']], function () {
Route::get('home', function () {
return view('home');
})->middleware('CustomAuth');
});
But this didn't work. Any idea how I can make sure that the session had started, or start it before the middleware is called? I'm using Laravel 5.3
The L5 middleware consists of 3 "types".
The configuration is found in the Kernel.php file for HTTP requests (typically App\Http\Kernel. There's global middleware which will run for all requests and is declared in $middleware, there's the route group middleware which will run for all requests for a given route group and is declared in $middlewareGroups, by default all routes declared in web.php are considered to be web routes so all the web middleware apply.
The 3rd type is route middleware. These are declared in the $routeMiddleware array in the form "middlewareName" => Middleware::class and can be used in any route e.g.
Route::get("/route", function () { /* route body */ })->middleware("middlewareName");
These run in order global > group > route middleware and the SessionStart middleware runs as part of the group middleware. Any other middleware that needs access to the session will need to be placed after the SessionStart middleware.
Clarification
It occurs to be when re-reading this that this implies that you need to declare the middleware in the $middeware variable to use them. This is not the case, the following syntax is also allowed:
Route::get("/route", function () {
/* route body */
})->middleware(Middleware::class);
However this syntax will not allow you to provide parameters to the middleware when you use them as you would for example with the authentication middleware when you do auth:api (where api would be a parameter passed to the middleware).

Attach middleware to all method

I have this in Laravel 4.2 Route::when('*', 'csrf', ['post']); that insert csrf verification to all post, how can I port to Larevel 5.2 ?
This is my own csrf, without using default provide by Laravel:
<?php
namespace App\Http\Middleware;
use Closure;
use Input;
class VerifyCsrfToken1
{
public function handle($request, Closure $next)
{
$token = $request->ajax() ? $request->header('X-CSRF-Token') : $request->input('_token');
if ($request->session()->token() === $token) {
return $next($request);
}
throw new TokenMismatchException;
}
}
I created my personal csrf middleware, but I don't know how to attach them on ALL post request
I want to attach it to all post via Route's facade. (file routes.php)
Thanks :)
Laravel 5 wires up middleware a bit differently, you won't be doing this through the Request facade.
You want to first register your Middleware as global. Open up app/Http/Kernel.php and add it to the global $middleware array.
protected $middleware = [
VerifyCsrfToken1::class
...
Then in your middleware class, check to see if it is handling a POST request. If not, have it just pass the request along without doing anything.
if($request->method() != "POST") {
// Move right along
return $next($request);
}
Side note: As you noted Laravel has a VerifyCsrfToken middleware baked in already. I'd advise trying to adapt this if possible.

Categories