I have my pusher key set and initialized within Laravel 5.3. When I test it on my local environment, it works. When I try to run the exact same code on our production environment, I get this error:
Pusher : Error : {"type":"WebSocketError","error":{"type":"PusherError","data":{"code":null,"message":"Auth info required to subscribe to private-App.User.16"}}}
I've confirmed the Pusher key is identical on both my local and production.
The WS initializes on both environments the same:
wss://ws.pusherapp.com/app/264P9d412196d622od64d?protocol=7&client=js&version=4.1.0&flash=false
The only difference that I can see, is that when our production server contacts the Laravel "broadcasting/auth" route, it simply receives true in the response body.
When my local contacts "broadcasting/auth" it gets this in the response:
{auth: "22459d41299d6228d64d:df5d393fe37df0k3832fa5556098307f145d7e483c07974d8e7b2609200483f8"}
Within my BroadcastServiceProvider.php:
public function boot()
{
Broadcast::routes();
// Authenticate the user's personal channel.
Broadcast::channel('App.User.*', function (User $user, $user_id) {
return (int)$user->id === (int)$user_id;
});
}
What could cause the broadcast/auth route to return simply true instead of the expected auth?
If you check PusherBroadcaster.php file, you will see that the response can be "mixed".
I think the documentation is saying about the default broadcast only.
The channel method accepts two arguments: the name of the channel and
a callback which returns true or false indicating whether the user is
authorized to listen on the channel.
This is the validAuthenticationResponse method inside PusherBroadcast.
/**
* Return the valid authentication response.
*
* #param \Illuminate\Http\Request $request
* #param mixed $result
* #return mixed
*/
public function validAuthenticationResponse($request, $result)
{
if (Str::startsWith($request->channel_name, 'private')) {
return $this->decodePusherResponse(
$this->pusher->socket_auth($request->channel_name, $request->socket_id)
);
}
return $this->decodePusherResponse(
$this->pusher->presence_auth(
$request->channel_name, $request->socket_id, $request->user()->getAuthIdentifier(), $result)
);
}
Just to give you another example, this is inside RedisBroadcast.
if (is_bool($result)) {
return json_encode($result);
}
Short explanation about this "auth request":
BroadcastManager instantiate all "available drivers" (Pusher, Redis, Log,etc) , and create the "auth" route (using BroadcastController + authenticate method).
When you call "auth", this will happen:
Call "broadc.../auth" route.
BroadcastManager will instantiate the proper driver (in your case Pusher)
PusherBroadcaster can throw an exception AccessDeniedHttpException if the user is not authenticated (the "user session" - Auth::user() is not defined/null) and is trying to access a private (or presence) channel type.
If the user is trying to access a private/presence channel and the user is authenticated (Auth::check()), Laravel will check if the auth. user can access the channel. (Check: verifyUserCanAccessChannel method).
After that, validAuthenticationResponse method will be called. This method will make a request to pusher with the user credentials and return an array. This array contains Pusher response (socket auth: https://github.com/pusher/pusher-http-php/blob/03d3417748fc70a889c97271e25e282ff1ff0ae3/src/Pusher.php#L586 / Presence Auth: https://github.com/pusher/pusher-http-php/blob/03d3417748fc70a889c97271e25e282ff1ff0ae3/src/Pusher.php#L615) which is a string.
Short answer:
Soo.. Pusher require this auth response. Otherwise you won't be able to connect/identify the user (wss://ws.pusherapp.com....).
Edit This is from the version 5.5 docs, not applicable here.
I think the issue maybe with using the '*' wildcard in the channel's name.
I use the following in both local and production:
Broadcast::channel("servers.{id}", function (Authenticatable $user, $id) {
return (int)$user->getAuthIdentifier() === (int)$id;
});
The problem happens because you did not set the proper BROADCAST_DRIVER in your production .env file (which is redis by default).
# before
BROADCAST_DRIVER=redis
# after
BROADCAST_DRIVER=pusher
Related
I am using third party REST API in my SYMFONY 4.3 app. My app requires checking if token is valid before any request. When is the best place to check if the token is valid and if not try to refresh before request in symfony? Any before request filter in symfony exists? or is there global object when I can fetch all request and if header is 401 I can perform specific action
Now I have central point in my app and all requests are passed through this function. But in future when I will have other request not passed through this function I have to make next function etc... and I am searching place where put isTokenValid code, I am thining about place like " call this function before any request to API "
Should i Use it?
https://symfony.com/doc/current/event_dispatcher/before_after_filters.html#token-validation-example
public function prepareRequest($method, $endPoint) {
.........
// Users can have many tokens connected to different accounts on third party app
$apiTokens = $user->getApiTokens();
/** #var ApiToken $apiToken */
foreach ($apiTokens as $apiToken) {
if ($this->isTokenValid($apiToken)) {
............. make request with specifed apiToken
}
public function isTokenValid(ApiToken $token): bool
{
if token is not valid return false
if token date expired try to refresh token
if token is not valid or refreshing token fails return false else return true
}
The solution I'd like to suggest is to use lexik/jwt-bundle I use it in almost all of mine front-end authentication projects for example you can customize the default response (JWT token not found / not valid) to return the response you desire. You can create both anonymous true or false routes for your purpose I guess anonymous should be true even though your token expired you will extend its lifetime. In case you want some insights put a comment to this answer and I'll provide as best as I can
My application is a single page app using Angular 1.x on the client side and Laravel 5.3 for my server/api. I easily managed to make the Auth0 authentication working on my client side (angular) and it successfully generates a token. Moving to my api (laravel), unfortunately I can't access any routes that is protected by the auth0.jwt middleware even though the Authorization header is present. Its response is a simple text that says Unauthorized user.
I'm using Chrome postman to test out the routes on my api.
I tried to trace down the function that is responsible for the response by checking the vendor\auth0\login\src\Auth0\Login\Middleware\Auth0JWTMiddleware.php and found out that the CoreException is being thrown.
Here's the handle method of the Auth0JWTMIddleware:
/**
* #param $request
* #param \Closure $next
*
* #return mixed
*/
public function handle($request, \Closure $next)
{
$auth0 = \App::make('auth0');
$token = $this->getToken($request);
if (!$this->validateToken($token)) {
return \Response::make('Unauthorized user', 401);
}
if ($token) {
try {
$jwtUser = $auth0->decodeJWT($token);
} catch (CoreException $e) {
return \Response::make('Unauthorized user', 401);
} catch (InvalidTokenException $e) {
return \Response::make('Unauthorized user', 401);
}
// if it does not represent a valid user, return a HTTP 401
$user = $this->userRepository->getUserByDecodedJWT($jwtUser);
if (!$user) {
return \Response::make('Unauthorized user', 401);
}
// lets log the user in so it is accessible
\Auth::login($user);
}
// continue the execution
return $next($request);
}
My suspect is that the token that generates from Auth0 has newer algorithm or something and the Laravel Auth0 package doesn't already supports it.
I followed exactly the documentation provided by Auth0 and I also cloned their sample projects just to make sure the configurations are correct but unfortunately it doesn't work also. Any thoughts or ideas on how can I solve my issue? Thanks in advance!
I had the same problem. There were 2 things I had to change in order to resolve. Credit to #Kangoo13 for the first suggestion:
1; Check that in your config/laravel-auth0.php file that secret_base64_encoded = false. You will notice in your Auth0 dashboard next to your key it states "The client secret is not base64 encoded"
'secret_base64_encoded' => false,
2; in the same config file, Check that 'supported' has the correct spelling. It would appear someone has incorrectly typed "suported", if you've just applied the default config file after running composer then chances are this is wrong!
Looking in JWTVerifier.php it does appear to cater for the misspelled key but it will default to 'HS256', Auth0 guide explicitly states you should be using 'RS256:
'supported_algs' => ['RS256'],
Hope that helps!
I read the suported_algs issue above but skim read it and missed the fact you have to correct the spelling for it to work, spent an extra day trying to figure it out before re-reading. Hopefully next person to read it sees this and doesn't miss the spelling issue! Thanks #user1286856
In my local environment I want all logs (all flags) to go the browser's console (BrowserConsoleHandler) and then to the default StreamHandler.
In production, I want the errors and other critical messages to go to an e-mail then stored in the database or (if fails) to a log file (default StreamHandler)
I want to set up this in a global middle-ware that I have created, which looks like this now:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Log;
use Monolog\Logger;
use Monolog\Handler\BrowserConsoleHandler;
class GlobalConfig
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
// Get Monolog instance
$monolog = Log::getMonolog();
// In local environment the logs will be shown on browser
if (App::environment('local')) {
// Show logs in browser console
$monolog -> pushHandler(new BrowserConsoleHandler());
} else {
// Here we set up for production
}
Log::debug("Browser handler working");
return $next($request);
}
}
This doesn't work (the message is stored in the log file only, not shown on console). What I can't figure out is how to let the Log facade know about this new handler, because here, as is obvious, things are only changed within the function's scope. I know I can do it in bootstrap/app.php but isn't it to early to get the environment? Also, if I need to save logs to the database, it must already be connected, I guess
You can try to do next in bootstrap\app.php
if(env('APP_ENV') == 'local') {
$app->configureMonologUsing(function($monolog) use ($app) {
$monolog->pushHandler(
$handler = new \Monolog\Handler\RotatingFileHandler(
$app->storagePath().'/logs/laravel.log',
$app->make('config')->get('app.log_max_files', 30),
\Monolog\Logger::DEBUG
)
);
$handler->setFormatter(new \Monolog\Formatter\LineFormatter(null, null, true, true));
$monolog->pushHandler(new \Monolog\Handler\NativeMailerHandler(
'to#mail.com',
'Log::error!',
'from#mail.com'
));
});
}
Idea here is to fully control your logging on live/local. Bad news - configureMonologUsing replace all default laravel loggers, so you need to configure all your logs manually here.
Middleware isn't really the place for this, unless you wanted to actually make a particular log entry at some point in the request cycle.
Your call to pushHandler belongs in bootstrap/app.php (in Laravel 5.2), according to the docs.
I would think it would be possible to extract this logic out into a provider in case it becomes complicated and you need to move it out of bootstrap/app.php
solution here
BrowserConsoleHandler sends the script snipped after finishing the php
script by register_shutdown_function(). At this time, Laravel already
sent the full response to the browser. So the script snipped from
BrowseConsoleHandler gets generated but never sent to the browser.
I defined a test which tests the creation of a user. The controller is set to redirect back to the same page on error (using validation through a generated App\Http\Requests\Request). This works correctly when manually clicking in a browser, but fails during a test. Instead of being redirected to:
http://localhost/account/create
The test redirects to (missing a slash):
http://localhostaccount/create
Neither of these urls are what I have setup in the .htaccess or in the $url variable in config/app.php. Which is (On OSX Yosemite):
http://~username/laravel_projects/projectname/public
I finally pinpointed the issue to have something to do with how the result of Request::root() is generated. Making a call to this outside of a test results in the expected value defined in .htaccess and $url. Inside the test it results in:
http://localhost
What configuration needs to change in order to get this function to return the correct value in both contexts?
I should also mention I made the painful upgrade from Laravel 4 to the current version 5.0.27.
****** UPDATE *******
I was able to figure out an acceptable solution/workaround to this issue!
In Laravel 5, FormRequests were introduced to help move validation logic out of controllers. Once a request is mapped to the controller, if a FormRequest (or just Request) is specified, this is executed before hitting the controller action.
This FormRequest by default handles the response if the validation fails. It attempts to construct a redirect based on the route you posted the form data to. In my case, possibly related to an error of mine updating from Laravel 4 to 5, this default redirect was being constructed incorrectly. The Laravel System code for handling the response looks like this:
/**
* Get the proper failed validation response for the request.
*
* #param array $errors
* #return \Symfony\Component\HttpFoundation\Response
*/
public function response(array $errors)
{
if ($this->ajax() || $this->wantsJson())
{
return new JsonResponse($errors, 422);
}
return $this->redirector->to($this->getRedirectUrl())
->withInput($this->except($this->dontFlash))
->withErrors($errors, $this->errorBag);
}
Notice how the returned redirect is NOT the same as calling Redirect::route('some_route'). You can override this response function by including use Response in your Request class.
After using Redirect::route() to create the redirect, the logic in my tests passed with the expected results. Here is my Request code that worked:
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use App\Http\Requests\Request;
use Response;
class AccountRequest 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 [
'email' => 'required|max:50|email|unique:users',
'password' => 'required|min:6',
'password_confirmation' => 'required|same:password'
];
}
public function response(array $errors){
return \Redirect::route('account_create');
}
}
The important part is that I called Redirect::route instead of letting the default response code execute.
Override the response function in the FormRequest validation handler to force the redirect to be constructed with Redirect::route('named_route') instead of allowing the default redirect.
You need to change config/app.php file's url value. Default value is http://localhost
Doc from config/app.php
This URL is used by the console to properly generate URLs when using the Artisan command line tool. You should set this to the root of your application so that it is used when running Artisan tasks.
I know this isn't an exact answer to your question since it is not a configuration update that solves the problem. But I was struggling with a related problem and this seems to be the only post on the internet of someone dealing with something similar - I thought I'd put in my two cents for anyone that wants a different fix.
Please note that I'm using Laravel 4.2 at the moment, so this might have changed in Laravel 5 (although I doubt it).
You can specify the HTTP_HOST header when you're testing a controller using the function:
$response = $this->call($method, $uri, $parameters, $files, $server, $content);
To specify the header just provided the $server variable as an array like so:
array('HTTP_HOST' => 'testing.mydomain.com');
When I did the above, the value produced for my Request::root() was http://testing.mydomain.com.
Again, I know this isn't a configuration update to solve you're issue, but hopefully this can help someone struggling with a semi-related issue.
If you tried changine config/app.php and it did not help.
it is better to use $_ENV - global variable in phpunit.
say, you want Request::root() to return 'my.site'
but you cannot touch phpunit.xml
you can simply set an env param like so
$_ENV['APP_URL'] = 'my.site';
and call $this->refreshApplication(); in your unittest.
viola, your request()->root() is giving you my.site now.
I have two authentication classes defined.
API Keys (APIKeyAuth)
OAUTH2 (OAUTH2Server)
In my index.php I have the following defined
$r = new Restler();
$r->addAuthenticationClass('APIKeyAuth');
$r->addAuthenticationClass('OAUTH2Server');
I then protect one of the rest methods for APIKeyAuth
/**
* #access protected
* #class APIKeyAuth{#requires apikey}
*/
public function .......etc
If I debug it , it goes through the first step and $authObj (see code below from restler.php) will
be APIKeyAuth. It checks __isAllowed and returns true ... which is good.
It then however goes through OAUTH2Server (which in my opinion it shouldn't as the rest method has
been decorated to use APIKeyAuth.
So it goes through and __isAllowed in OAUTH2Server is false so then the user will get a Unauthorzied response.
foreach ($this->authClasses as $authClass) {
$authObj = Scope::get($authClass);
if (!method_exists($authObj,
Defaults::$authenticationMethod)
) {
throw new RestException (
500, 'Authentication Class ' .
'should implement iAuthenticate');
} elseif (
!$authObj->{Defaults::$authenticationMethod}()
) {
throw new RestException(401);
}
}
Do I need to alter the OAUTH2 Server to check if its using an API Key and add logic ? (seems wrong approach).
Restler upto RC5 handles authentication classes serially, meaning that all the authentication classes must return true to go through the protected api call
Since RC6 this has changed to parallel, meaning that any one of the authentication class can allow access to the protected api