I am using Socialite Providers for Reddit with Laravel 5.2. In an attempt to address a Reddit error ('too many requests') during authentication I am having difficulty overriding the Socialite Provider method to add a custom header ('User-Agent') to the authentication request.
Here are the details.
The original problem is that I am getting an error during authentication with Reddit:
ClientException in RequestException.php line 107:
Client error: `POST https://ssl.reddit.com/api/v1/access_token`
resulted in a `429 Too Many Requests` response:
{"error": 429}
I can fix the error by modifying the vendor/socialiteproviders\reddit\src\Provider.php method called getAccessToken to include a header called 'User-Agent'.
$response = $this->getHttpClient()->post($this->getTokenUrl(), [
'headers' => ['Accept' => 'application/json',
'User-Agent' => 'mydomain.com: v1.0 (by /u/reddituser)'],
'auth' => [$this->clientId, $this->clientSecret],
'form_params' => $this->getTokenFields($code),
]);
The problem with this solution is that my modifications can easily be lost with an update to the vendor files. Instead I would like to extend the Providers class and override the getAccessToken method.
I am able to get the ServiceProvider to register my custom method but I can't seem to get the custom method to actually run in place of the original.
Here is my code
app\Library\CustomRedditProvider.php (notice the dd which is never executed)
namespace app\Library\redditOverride;
use SocialiteProviders\Reddit\Provider;
class CustomRedditProvider extends Provider {
// public function getAccessToken($code)
public function getAccessToken($code)
{
dd('RedditCustomProvider getAccessToken');
$response = $this->getHttpClient()->post($this->getTokenUrl(), [
'headers' => ['Accept' => 'application/json',
'User-Agent' => 'mydomain.com: v1.0 (by /u/reddituser)'],
'auth' => [$this->clientId, $this->clientSecret],
'form_params' => $this->getTokenFields($code),
]);
$this->credentialsResponseBody = json_decode($response->getBody(), true);
return $this->parseAccessToken($response->getBody());
}
}
app/Providers/RedditOverrrideServiceProvider.php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class RedditOverrideServiceProvider extends ServiceProvider
{
/**
* Bootstrap the application services.
*
* #return void
*/
public function boot()
{
//
}
/**
* Register the application services.
*
* #return void
*/
public function register()
{
$this->app['customredditprovider'] = $this->app->share(function($app)
{
return new CustomRedditProvider();
});
}
}
config/app.php
\SocialiteProviders\Manager\ServiceProvider::class,
App\Providers\RedditOverrideServiceProvider::class,
composer.json
"autoload": {
"classmap": [
"database",
"app/library"
],
Related
I'm using external identity provider to authenticate users, created a SPA client (got client_id & client_secret), configured API with audience & scope, so once users authenticated they will get access_token (will be authorized) to access multiple custom micro-services (APIs).
When my custom API receives a request with a bearer Access Token (JWT) the first thing to do is to validate the token. In order to validate JWT I need to follow these steps:
Check that the JWT is well formed (Parse the JWT)
Check the signature. My external identity provider only supports RS256 via the JWKS (JSON Web Key Set) URL (https://{domain}/.well-known/jwks.json), so I can get my public key following this URL.
Validate the standard claims
Check the Application permissions (scopes)
There are a lot of packages/libraries (i.e. https://github.com/tymondesigns/jwt-auth) to create JWT tokens but I can't find any to validate it using those steps above. Could anyone please help to find suitable Laravel/PHP package/library or move me to the right direction in order to achieve my goals (especially point #2).
I did something similar in the past, I don't know if this may help but I'll give it a try. To use a public key, you should download it, put it somewhere on the disk (storage/jwt/public.pem for example) and then link it in the jwt config config/jwt.php with the ALGO (you can see supported algorithms here
'keys' => [
// ...
'public' => 'file://'.storage_path('jwt/public.pem'),
// ...
],
'algo' => 'RS256',
Then, you should have a custom Guard, let's call it JWTGuard:
<?php
namespace App\Guard;use App\Models\User;
use Illuminate\Auth\GuardHelpers;
use Illuminate\Contracts\Auth\Guard;
use Illuminate\Http\Request;
use Tymon\JWTAuth\JWT;class JWTGuard implements Guard
{
use GuardHelpers;
/**
* #var JWT $jwt
*/
protected JWT $jwt;
/**
* #var Request $request
*/
protected Request $request;
/**
* JWTGuard constructor.
* #param JWT $jwt
* #param Request $request
*/
public function __construct(JWT $jwt, Request $request) {
$this->jwt = $jwt;
$this->request = $request;
}
public function user() {
if (! is_null($this->user)) {
return $this->user;
}
if ($this->jwt->setRequest($this->request)->getToken() && $this->jwt->check()) {
$id = $this->jwt->payload()->get('sub');
$this->user = new User();
$this->user->id = $id;
// Set data from custom claims
return $this->user;
}
return null;
}
public function validate(array $credentials = []) { }
}
This should do all your logic of validation, I used a custom user implementation, the class signature was like:
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Database\Eloquent\Model;
class User extends Model implements AuthenticatableContract {
// custom implementation
}
Finally, you should register the guard in the AuthServiceProvider and in the auth config
public function boot()
{
$this->registerPolicies();
$this->app['auth']->extend(
'jwt-auth',
function ($app, $name, array $config) {
$guard = new JWTGuard(
$app['tymon.jwt'],
$app['request']
);
$app->refresh('request', $guard, 'setRequest');
return $guard;
}
);
}
then allow it in the config
<?php
return [
'defaults' => [
'guard' => 'jwt',
'passwords' => 'users',
],
'guards' => [
// ...
'jwt' => [
'driver' => 'jwt-auth',
'provider' => 'users'
],
],
// ...
];
You can then use it as a middleware like this:
Route::middleware('auth:jwt')->get('/user', function() {
return Auth::user();
}
Does this sound good to you?
In the end I've used the Auth0 SDK for Laravel - https://auth0.com/docs/quickstart/backend/laravel/01-authorization. Nice and clean solution.
So I use a Service Class (extends from TYPO3\CMS\Core\Authentication\AuthenticationService) to authenticate our Frontend Users using OAuth2. These Services are automatically instantiated and called via Typos own Middleware: FrontendUserAuthenticator.
In this class I used to save data from the authentication result to $GLOBALS['TSFE']->fe_user using setKey('ses', 'key', 'data'), which seems is not possible anymore since v10. How would I go about still doing this?
The documentation is sparse
https://docs.typo3.org/c/typo3/cms-core/master/en-us/Changelog/9.4/Deprecation-85878-EidUtilityAndVariousTSFEMethods.html
https://docs.typo3.org/m/typo3/reference-coreapi/10.4/en-us/ApiOverview/Context/Index.html
I've tried the following:
constructor injecting the TSFE using DI
class FrontendOAuthService extends AuthenticationService
{
public function __construct(TypoScriptFrontendController $TSFE) {
=> LogicException: TypoScriptFrontendController was tried to be injected before initial creation
changing the Middlewares order to have it instantiate before the Auth Middleware
(packages/extension_name/Configuration/RequestMiddlewares.php)
return [
'frontend' => [
'typo3/cms-frontend/tsfe' => [
'disabled' => true,
],
'vendor/extension_name/frontend-oauth' => [
'target' => \TYPO3\CMS\Frontend\Middleware\TypoScriptFrontendInitialization::class,
'before' => [
'typo3/cms-frontend/authentication',
],
'after' => [
'typo3/cms-frontend/eid',
'typo3/cms-frontend/page-argument-validator',
],
],
],
];
=> UnexpectedValueException: Your dependencies have cycles. That will not work out.
instantiating the TSFE myself
/** #var ObjectManager $objectManager */
$objectManager = GeneralUtility::makeInstance(ObjectManager::class);
/** #var DealerService $dealerService */
$lang = $site->getDefaultLanguage();
$siteLanguage = $objectManager->get(SiteLanguage::class, $lang->getLanguageId(), $lang->getLocale(), $lang->getBase(), []);
/** #var TypoScriptFrontendController $TSFE */
$TSFE = $objectManager->get(
TypoScriptFrontendController::class,
GeneralUtility::makeInstance(Context::class),
$site,
$siteLanguage,
GeneralUtility::_GP('no_cache'),
GeneralUtility::_GP('cHash')
);
=> the $TSFE->fe_user is an emptystring ("")
using the UserAspect
/** #var Context $context */
$context = GeneralUtility::makeInstance(Context::class);
$feUser = $context->getAspect('frontend.user');
$feUser->set...
=> Aspects are read-only
adding vars to the user data in the getUser method of the AuthenticationService
(packages/extension_name/Classes/Service/FrontendOAuthService.php)
public function getUser()
{
$user = allBusinessCodeHere();
$user['my_own_key'] = 'myData';
return $user;
=> is not propagated to the UserAspect(frontend.user) nor the $TSFE->fe_user
I'm out of ideas guys.
I had a similar problem when i wanted to use redirects with record links.
I ended up disabling the original redirect middleware and adding my own with a mocked version of tsfe.
The extension can be found here:
https://github.com/BenjaminBeck/bdm_middleware_redirect_with_tsfe
Late to the party, but I had the same issue and was able to solve it:
https://docs.typo3.org/c/typo3/cms-core/master/en-us/Changelog/10.0/Breaking-88540-ChangedRequestWorkflowForFrontendRequests.html states:
Storing session data from a Frontend User Session / Anonymous session
is now triggered within the Frontend User
(frontend-user-authenticator) Middleware, at a later point - once the
page was generated. Up until TYPO3 v9, this was part of the
RequestHandler logic right after content was put together. This was
due to legacy reasons of the previous hook execution order. Migration
Consider using a PSR-15 middleware instead of using a hook, or
explicitly call storeSessionData() within the PHP hook if necessary.
In my MyAuthenticationService extends AbstractAuthenticationService in method getUser() I set $_SESSION['myvendor/myextension/accessToken'] to the token received by the external oauth service. In my SaveSessionMiddleware I save this token to the FrontendUserAuthentication object using setKey() which by then is available:
EXT:myextension/Configuration/RequestMiddlewares.php
return [
'frontend' => [
'myvendor/myextension/save-session-middleware' => [
'target' => \MyVendor\MyExtension\Middleware\SaveSessionMiddleware::class,
'after' => [
'typo3/cms-frontend/authentication',
],
]
]
];
EXT:myextension/Classes/Middleware/SaveSessionMiddleware.php
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use TYPO3\CMS\Frontend\Authentication\FrontendUserAuthentication;
class SaveSessionMiddleware implements MiddlewareInterface {
/**
* #param ServerRequestInterface $request
* #param RequestHandlerInterface $handler
* #return ResponseInterface
*/
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface {
if (!empty($_SESSION['myvendor/myextension/accessToken'])) {
$this->getFrontendUserAuthentication()->setKey(
'ses',
'myvendor/myextension/accessToken',
$_SESSION['myvendor/myextension/accessToken']);
unset($_SESSION['myvendor/myextension/accessToken']);
}
return $handler->handle($request);
}
private function getFrontendUserAuthentication(): FrontendUserAuthentication {
return $GLOBALS['TSFE']->fe_user;
}
}
I am creating an API using API-Platform and have set up my user entity etc using the standard symfony security bundle (https://symfony.com/doc/current/security.html#retrieving-the-user-object)
I have the login working with REST at {url}/api/login using JWT but I cannot see any way of sending my login details with GraphQL
The API-platform documentation shows how to set up security and how to setup GraphQL separately but doesn't really show how to combine them.
https://api-platform.com/docs/core/graphql
https://api-platform.com/docs/core/fosuser-bundle
How do I make the login accessible in GraphQL?
Currently, I only have the createUser updateUser and deleteUser mutations, I assume I would need an authenticateUser one?
Yes, you'll need a custom mutation for the login.
Assuming you are using the API Platform standard docs for the API, you are using JWT to authenticate your calls, you need a UserMutationResolver for auth:
<?php
namespace App\Resolver;
use ApiPlatform\Core\GraphQl\Resolver\MutationResolverInterface;
use App\Entity\User;
use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTTokenManagerInterface;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Doctrine\ORM\EntityManagerInterface;
final class UserMutationResolver implements MutationResolverInterface
{
public function __construct(
private UserPasswordEncoderInterface $userPasswordEncoder,
private JWTTokenManagerInterface $JWTManager,
private EntityManagerInterface $em,
)
{}
/**
* #param User|null $item
*
* #return User
*/
public function __invoke($item, array $context)
{
// Mutation input arguments are in $context['args']['input'].
if ($context["info"]->fieldName == 'loginUser') {
$userRepository = $this->em->getRepository("App:User");
$user = $userRepository->findOneBy(['email' => $item->getEmail()]);
if ($this->userPasswordEncoder->isPasswordValid($user, $item->getPlainPassword())) {
$token = $this->JWTManager->create($item);
$user->setToken($token);
}
return $user;
}
}
}
Then you add that custom mutation to the User entity. Be sure to add the names of the auto-generated mutations/queries or they will disappear (item_query, create, update, delete, collection_query). You'll also need to disable some of the stages, since this is a mutation Api Platform will try and save this as a new user, which we don't want, so as you see below, 'write' => false and 'validate' => false
// api/src/Entity/User.php
// imports etc .
// ...
#[ApiResource(
normalizationContext: ["groups" => "user:read"],
denormalizationContext: ["groups" => "user:write"],
attributes: [
'write' => true,
],
graphql: [
'item_query',
'create',
'update',
'delete',
'collection_query',
'login' => [
'mutation' => UserMutationResolver::class,
'write' => false,
'validate' => false,
'args' => [
'email' => ['type' => 'String!', 'description'=> 'Email of the user ' ],
'password' => ['type' => 'String!', 'description'=> 'Password of the user ' ]
]
],
],
iri:"http://schema.org/Person",
)]
#[UniqueEntity(fields: ["email"])]
class User implements UserInterface
{
// ...
This will create a mutation that you can access like this:
mutation {
loginUser(input: {email:"test1#test.com", password:"password"}) {
user {
token
}
}
}
and the result should look something like this:
{
"data": {
"loginUser": {
"user": {
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpYXQiOjE2MTgzNjM1NDQsImV4cCI6MTYxODM2NzE0NCwicm9sZXMiOlsiUk9MRV9VU0VSIl0sInVzZXJuYW1lIjoidGVzdDFAdGVzdC5jb20ifQ.pSoAyNcaPa4MH2cxaAM4LJOGvirHfr94GMf_k20eXlF1LAaJyXRraKyC9hmBeKSUeAdIgowlfGFAHt96Z4EruBlkn2mbs3mj3uBWr2zqfNTVyQcicJDkJCO5EpbpexyLO5igD9qZU__4ctPvZcfWY-dJswSfiCTP1Uz0BiGFsGqb72chd8Rhn5Btls-D6b9Uuzl9ZZeLj2pIuBA-yi_CMm3CzopKIJ1NySMT8HyvafHcTdfpzFWFPoUqxkVAzt4U6tqBpEnTqmwRW_3kTisJhIY9xH2uXKghz2VWM6mvTL1PahZgbwLqsVb_sBOOEtiASpGf8WNc1uXtKNhBCb_YJw"
}
}
}
}
I cannot see any way of sending my login details with GraphQL
Auth protected queries should be sent with Authorization header. Exact method depends on client-side technology, f.e. Apollo client supports this by middleware.
You can use existing REST login endpoint (fetch/get token) or create login mutation - example.
Another inspiration can come from a more complex example apollo-universal-starter-kit
I have setup new laravel v5.3 project and install elastic search driver to implement elastic search via composer. But when I reload my page then I always receive This page isn’t working even the elastic search is running on my system below is my complete code that I code.
composer.json
"require": {
"php": ">=5.6.4",
"elasticsearch/elasticsearch": "^6.0",
"laravel/framework": "5.3.*"
},
web.php
Route::get('/',array('uses' => 'ElasticSearch#addPeopleList'));
Controller
<?php
namespace App\Http\Controllers;
class ElasticSearch extends Controller
{
// elastic
protected $elastic;
//elastic cliend
protected $client;
public function __construct(Client $client)
{
$this->client = ClientBuilder::create()->build();
$config = [
'host' =>'localhost',
'port' =>9200,
'index' =>'people',
];
$this->elastic = new ElasticClient($config);
}
public function addPeopleList(){
echo "<pre>";
print_r($this->$elastic);
exit;
}
}
But when I refresh the page then This page isn’t working i received this message and page not loaded one thing that I want to let you know that I made no changes in app.php file of configuration. Please eduacate to solve this issue.
if You want to instantiate an elastic client with some configuration, You should use method ClientBuilder::fromConfig(array $config).
In your case it should be
<?php
$client = ClientBuilder::fromConfig([
'hosts' => [ 'localhost:9200' ]
]);
As You can notice above hosts must be provided as array.
Also I'm not sure that Elasticsearch client that You use have ElasticClient class.
Also if You provided actual code from your controller than it contains an error. You should call class properties like that: print_r($this->client) (without $ near the property name).
Finaly your controller should looks like this:
<?php
namespace App\Http\Controllers;
use Elasticsearch\ClientBuilder;
class ElasticSearch extends Controller
{
/**
* #var \Elasticsearch\Client
*/
protected $client;
public function __construct()
{
$this->client = ClientBuilder::fromConfig([
'hosts' => [
'localhost:9200',
],
]);
}
public function addPeopleList(){
echo "<pre>";
print_r($this->client);
exit;
}
}
And to add a document to the index You need to call this command according to the official documentation
$params = [
'index' => 'my_index',
'type' => 'my_type',
'id' => 'my_id',
'body' => ['testField' => 'abc']
];
$response = $client->index($params);
print_r($response);
Official documentation can be found here https://github.com/elastic/elasticsearch-php
P.S. Sorry for my English. It is far from perfect.
Laravel Version: 5.3.*
PHP Version: 5.6.17
Database Driver & Version:
mysql
Description:
According to Laravel 5.3 documentation when broadcasting events on private or presence channels, in the boot method of the BroadcastServiceProvider one must provide a callback that resolves if an user has authorization to listen to that channel to the Broadcast facade method channel. This method should return a boolean. In the the BroadcastServiceProvider method boot we should also include Broadcast::routes() that will define the auth route that the client will call to check for permission on the channel. This routes method can receive an array of attributes to apply to the route. Now it's where it gets weird. When the client call this route no matter what callback I passed to the Broadcast::channel method it will give me a 403 forbidden unless (and now comes the weirdest part) I provide an array to the Broadcast::routes with a key named prefix and a value of whatever. If the key is not prefix it will go back to 403 forbidden.
HttpException in PusherBroadcaster.php line 42:
My setup follows. I'm for sure doing something wrong but after a lot of ours trying to understand I can't figure it out. Can someone give an hint?
Steps To Reproduce:
I have created a simple event:
<?php
namespace App\Events;
use App\Models\Presentation;
use Illuminate\Broadcasting\Channel;
use Illuminate\Queue\SerializesModels;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
class PresentationCreated implements ShouldBroadcast
{
use InteractsWithSockets, SerializesModels;
public $presentation;
/**
* Create a new event instance.
*
* #return void
*/
public function __construct(Presentation $presentation)
{
$this->presentation = $presentation;
}
/**
* Get the channels the event should broadcast on.
*
* #return Channel|array
*/
public function broadcastOn()
{
return new PrivateChannel('presentation');
}
}
that i trigger by calling event(new PresentationCreated($presentation));
I have installed "pusher/pusher-php-server": "^2.5.0" and created an account in pusher.
I put my pusher credentials in .env:
BROADCAST_DRIVER=pusher
PUSHER_APP_ID=*****
PUSHER_APP_KEY=*****************
PUSHER_APP_SECRET=****************
PUSHER_APP_CLUSTER=**
in my config\broadcast.php I have:
'pusher' => [
'driver' => 'pusher',
'key' => env('PUSHER_APP_KEY'),
'secret' => env('PUSHER_APP_SECRET'),
'app_id' => env('PUSHER_APP_ID'),
'options' => [
'cluster' => 'eu',
'encrypted' => true,
],
],
My client side:
this.Echo = new Echo({
broadcaster: 'pusher',
key: typeof handover.pak !== 'undefined' ? handover.pak : '',
cluster: 'eu'
});
this.Echo.private(`presentation`)
.listen('PresentationCreated', (e) => {
console.log(e, 'raposa')
});
And finally the BroadcastServiceProvider:
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Broadcast;
class BroadcastServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
Broadcast::routes();
//The commented line would make the authorization pass even if I return false bellow
//Broadcast::routes(['prefix' => 'I do not know what I am doing']);
/*
* Authenticate the user's personal channel...
*/
Broadcast::channel('presentation', function ($user) {
return false;
});
}
}
EDIT
Thanks to #yazfield answer I was able to understand what was going on. The http error was due to the $request->user() being null. That was because I was not passing the additional middlewares that my route namespace was using. By doing Broadcast::routes(['middleware' => ['web', 'clumsy', 'admin-extra']]); I was able to solve the problem.
This Laravel issue also helped me getting the grasp of the thing.
By giving a parameter to routes you're setting routes attributes and overriding the attributes that default to 'middleware' => ['web'], which basically means you're not using any of the web middlewares when you give any array without middleware attribute, you're not verifying crsfToken...etc.