I've built an example Laravel project using mongodb (Jenssegers Mongodb) and I use passport in Laravel 5.4 follow by this document.
Everything work fine until I took an access token to Postman for test, now take a look at my api.php route
Route::middleware('auth:api')->get('/user', function (Request $request) {
return $request->user();
});
In postman, I setup two headers that are Accept: application/json and Authorization: Bearer $TOKEN and I'm very sure that my access token is not the copy missing fault but still getting error.
{
"error": "Unauthenticated."
}
Things I've tried
I have overwrite default id field in model User.php
use Authenticatable, Authorizable, CanResetPassword, Notifiable, HasApiTokens;
protected $collection = 'users';
protected $fillable = ['username', 'email', 'password', 'name'];
protected $primaryKey = '_id';
I also modify the token expiration time in AuthserviceProvider.php like so
public function boot()
{
$this->registerPolicies();
Passport::routes();
Passport::tokensExpireIn(Carbon::now()->addYears(20));//You can also use addDays(10)
Passport::refreshTokensExpireIn(Carbon::now()->addYears(20));//You can also use addDays(10)
Passport::pruneRevokedTokens(); //basic garbage collector
Passport::tokensCan([
'conference' => 'Access your conference information'
]);
}
And some other ways but still not work.
UPDATE for debug infomation
When I add try catch in to public/index.php, an error apperead
League\OAuth2\Server\Exception\OAuthServerException: The resource owner or authorization server denied the request. in /data/www/public_html/xxxx/vendor/league/oauth2-server/src/Exception/OAuthServerException.php:165
Stack trace:
#0 /data/www/public_html/xxxx/vendor/league/oauth2-server/src/AuthorizationValidators/BearerTokenValidator.php(66): League\OAuth2\Server\Exception\OAuthServerException::accessDenied('Access token ha...')
#1 /data/www/public_html/xxxx/vendor/league/oauth2-server/src/ResourceServer.php(82): League\OAuth2\Server\AuthorizationValidators\BearerTokenValidator->validateAuthorization(Object(Zend\Diactoros\ServerRequest))
......
When I check file vendor\league\oauth2-server\src\AuthorizationValidators\BearerTokenValidator.php at line 66. Seems my access token has been revoked, but in my database revoked column still false, and this access token is brand new, I just created in few minutes ago.
if ($this->accessTokenRepository->isAccessTokenRevoked($token->getClaim('jti'))) {
throw OAuthServerException::accessDenied('Access token has been revoked');
}
Any idea?
After few hours of digging deep in to vendor\league\oauth2-server\src\AuthorizationValidators\BearerTokenValidator.php I was found a solution.
The problem is in file vendor\laravel\passport\src\TokenRepository.php at function public function find($id) line 27
public function find($id)
{
return Token::find($id);
}
Because I use Mobodb and this function is use use Jenssegers\Mongodb\Eloquent\Model and it search for _id instead of id. So, just need to make change find function like this.
public function find($id)
{
return Token::where('id', $id)->first();
}
Although modify vendor file is not recommend, but in this case I have to do that, hopefully in next version, author of Laravel passport will release a passport support mongoDB.
Hope this help someone.
Related
I'm configuring an external identity provider in my Keycloak instance and trying to get it to validate the tokens using a external JWKS URL. Using the converted PEM from JWKS works fine, the using the URL is not working.
The token validation fails upon login with the following message:
[org.keycloak.broker.oidc.AbstractOAuth2IdentityProvider] (default task-4) Failed to make identity provider oauth callback: org.keycloak.broker.provider.IdentityBrokerException: token signature validation failed
I debugged the Keycloak server get more on the problem and found a "problem" in class JWKSUtils:
/**
* #author Marek Posolda
*/
public class JWKSUtils {
//...
public static Map<String, KeyWrapper> getKeyWrappersForUse(JSONWebKeySet keySet, JWK.Use requestedUse) {
Map<String, KeyWrapper> result = new HashMap<>();
for (JWK jwk : keySet.getKeys()) {
JWKParser parser = JWKParser.create(jwk);
if (jwk.getPublicKeyUse().equals(requestedUse.asString()) && parser.isKeyTypeSupported(jwk.getKeyType())) {
KeyWrapper keyWrapper = new KeyWrapper();
keyWrapper.setKid(jwk.getKeyId());
keyWrapper.setAlgorithm(jwk.getAlgorithm());
keyWrapper.setType(jwk.getKeyType());
keyWrapper.setUse(getKeyUse(jwk.getPublicKeyUse()));
keyWrapper.setVerifyKey(parser.toPublicKey());
result.put(keyWrapper.getKid(), keyWrapper);
}
}
return result;
}
//...
}
The if fails with a NullPointerException because the call jwk.getPublicKeyUse() returns null.
But I found out that it's null because the JWKS URL returns a single key without the attribute use, which is optional according to the specification. [https://www.rfc-editor.org/rfc/rfc7517#section-4.2]
Keycloak only accepts JWKS URLs that return all keys with the attribute use defined. But the IdP I'm trying to connect does not return that attribute in the key.
Given that situation, to who should I file an issue, the IdP or to Keycloak? Or is there something I'm doing wrong in the configuration?
I filed an issue with Keycloak about this exact problem in August 2019.
Their answer:
Consuming keys without validating alg and use is dangerous as such
Keycloak requires these to be present.
In my case, I contacted the IdP and they were able to populate the "use" parameter. If that is not an option, then you're pretty much stuck with your workaround.
For some reason I get this exception when I try to log in with Laravel Socialite with either Facebook or Google:
InvalidStateException in AbstractProvider.php line 199:
The exception are thrown from my SocialiteController, when it tries to get the user from the facebook driver.
public function callback(SocialAccountService $service, $provider)
{
try {
var_dump(Socialite::driver('facebook')->user());
Here are the part of AbstractProvider.php that seem to throw the actual exception:
public function user()
{
if ($this->hasInvalidState()) {
throw new InvalidStateException;
}
I have been following this tutorial https://blog.damirmiladinov.com/laravel/laravel-5.2-socialite-twitter-login.html#.WFK0BfnhCUk. The login have worked fine until last week, for some reason.
I read other articles saying I should change config/session.php so domain is not null but my current domain (in my case localhost:8000 since I run local with XAMPP), and refresh Laravel cache etc. But it did not work.
I have discovered that my Laravel application cookies where missing, which caused the InvalidStateException exception.
I also noticed that the cookies were not recreated after each HTTP request. When I changed the domain value in Config/Session.php from my current one ("localhost") to null (the default value), then the cookies were recreated again.
Try
session()->put('state', $request->input('state'));
$user = Socialite::driver('facebook')->user();
I would like to be able to manually submit a symfony form with data I create but it seems I am missing the CSRF token in the submitted data and so the validation fails.
The form is simple - just one field (let's say 'name' for argument) as a text field with no constraints.
$data = [];
if ($someCondition) {
$data['name'] = 'steve';
}
$form = $this->createForm('FooType', $data);
if (!empty($data)) {
$form->submit($data);
} else {
$form->handleRequest($request);
}
if ($form->isSubmitted() && $form->isValid()) {
// do something
}
if I set $data['name'] the form submits but I get The CSRF token is invalid. Please try to resubmit the form.
so obviously, I am missing the token. I know I can disable the CSRF protection, but I don't want to do this as there is also an option to 'normally' interact with the form.
How to I submit a proper token or override this behavior?
I see you tagged this as symfony2 question. With that in mind, you can supply a valid CSRF token by injecting it into your data, thought, solution differ in <2.6 and >=2.6 version, as far as I'm aware.
Pre 2.6 version:
$provider = $this->get('form.csrf_provider');
$token = $provider->generateCsrfToken(''); // INTENTION = empty_string, by default
$data['<<YOUR_FORM_NAME']['_token'] = $token; // be sure to change the form name
Versions 2.6+
The thing is a bit different as forms now use TokenManagerInterface provided by Security component:
$tokenId = ....;
$token= (string) $this->get('security.csrf.token_manager')->getToken($tokenId);
$tokenValue = $token->getValue();
Now, the $tokenId can be many things, as described in a Form's public test:
$tokenId = $options['csrf_token_id'] ?:
($form->getName() ?:
get_class($form->getConfig()->getType()->getInnerType()));
But if you look into this, the default will be $form->getName(), up until 2.8. I think that 2.8 removed abstraction from the getName() method, thus, the 2.8 and later will user the clunky value:
get_class($form->getConfig()->getType()->getInnerType())
This all holds true, unless you injection csrf_token_id option in your form type.
UPDATE:
Ok, so it seems my bad presumption was about the token key. While you did get the valid token, that one was not used. In my example, I had separate FormType namespaced AppBundle\Form\SomeFooType and the actual token id that was used was some_foo.
I have made a pastebin of the working example (version 2.8.8, same worked in 3.1.2 as expected): http://pastebin.com/ks2jSeh7
Hope this helps a bit.
Quick background: I'm fairly experienced with PHP, but needed to build my first RESTful API. I figured I'd try Laravel (5.2) and am starting to feel pretty comfortable with it.
I started adding auth to my project over the weekend and I am really struggling to get it working. I got the basic Laravel Auth middleware working quickly, but I think I need to be using OAuth2 for production (I will be building a mobile app that will connect up to this server). I'm using the Luca Degasperi OAuth2 package, which seems to be pretty popular.
I reviewed the actual documentation: https://github.com/lucadegasperi/oauth2-server-laravel/tree/master/docs#readme)
I also went through this tutorial: https://medium.com/#mshanak/laravel-5-token-based-authentication-ae258c12cfea#.5lszb67xb
And, most recently, I found this thread about the need to seed the OAuth tables before anything will work: https://github.com/lucadegasperi/oauth2-server-laravel/issues/56
That's all great, but there are some minor differences in the most recent distribution of Laravel. For example, /app/Http/Kernel.php is slightly different from what's shown in some of the examples I found because it now uses middleware groups. I thought I handled those differences correctly (I added the OAuthExceptionHandlerMiddleware class to the 'web' section of $middlewareGroups instead of $middleware). I got my seeder working (the current oauth_scopes table only allows you to supply a description, so I had to slim down what was provided in the third link above).
If I put a test route in my 'web' group in routes.php, I would have thought this would require OAuth because I added OAuth to the 'web' middleware group in Kernel.php. That's not the case. My route works with no authentication if I do that.
I then explicitly added the OAuth middleware to my test route as follows:
Route::get('tests/events', ['middleware' => 'oauth', function() {
$events = App\Event::get();
return response()->json($events);
}]);
That causes a 500 error ("ErrorException in OAuth2ServerServiceProvider.php line 126: explode() expects parameter 2 to be string, object given").
I'm to feel pretty lost. Each of these packages seems to be shifting so quickly that there's no complete documentation on how to get this up and running.
What else do I need to do to get this functioning?
The following link is what finally got me un-stuck:
https://github.com/lucadegasperi/oauth2-server-laravel/blob/master/docs/authorization-server/password.md
Now that I have it working, I'll try and make this a complete how-to FOR PASSWORD GRANT TYPES ONLY. I didn't play with other grant types. So this assumes you're building something like a RESTful API where users will connect to it with a client app that you're going to build. So users will create a user account in your system and then when they send a REST request, the OAuth2 package will authenticate them and send them a token to stay logged in.
I'm using Laravel 5.2 and already had the basic Auth package up and running. Be advised that a lot of these steps seem to change even with incremental releases of Laravel or the OAuth2 package.
The first part of getting this working is fairly well documented already (https://github.com/lucadegasperi/oauth2-server-laravel/tree/master/docs#readme), but here's a summary just in case...
Edit the require section of your composer.json file to look something like this:
"require": {
"php": ">=5.5.9",
"laravel/framework": "5.2.*",
"lucadegasperi/oauth2-server-laravel": "5.1.*"
},
Run composer update to download the package.
Open your config/app.php file and add the following two lines to the end of the providers section:
LucaDegasperi\OAuth2Server\Storage\FluentStorageServiceProvider::class,
LucaDegasperi\OAuth2Server\OAuth2ServerServiceProvider::class,
Also in config/app.php, add this line to the aliases array:
'Authorizer' => LucaDegasperi\OAuth2Server\Facades\Authorizer::class,
Now we start to do things a little differently from the documentation to accommodate the current version of Laravel...
Open app/Http/Kernel.php. Laravel now uses groups and it didn't used to. Update your $middlewareGroups to look like this:
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
//Added for OAuth2 Server
\LucaDegasperi\OAuth2Server\Middleware\OAuthExceptionHandlerMiddleware::class,
//Commented out for OAuth2 Server
//\App\Http\Middleware\VerifyCsrfToken::class,
],
'api' => [
'throttle:60,1',
],
];
Also in app/Http/kernel.php, update $routeMiddleware to look like this:
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'can' => \Illuminate\Foundation\Http\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
//Added for OAuth2 Server
'oauth' => \LucaDegasperi\OAuth2Server\Middleware\OAuthMiddleware::class,
'oauth-user' => \LucaDegasperi\OAuth2Server\Middleware\OAuthUserOwnerMiddleware::class,
'oauth-client' => \LucaDegasperi\OAuth2Server\Middleware\OAuthClientOwnerMiddleware::class,
'check-authorization-params' => \LucaDegasperi\OAuth2Server\Middleware\CheckAuthCodeRequestMiddleware::class,
'csrf' => App\Http\Middleware\VerifyCsrfToken::class,
];
You now have to set up your grant types. You used to do this all in one place in config\oauth2.php using an array with a closure for callback. With the most recent version of the OAuth2 server package, you can't use a closure for callback anymore. It has to be a string. So your grant_types should look something like this:
'grant_types' => [
'password' => [
'class' => '\League\OAuth2\Server\Grant\PasswordGrant',
'callback' => '\App\PasswordGrantVerifier#verify',
'access_token_ttl' => 3600
]
]
access_token_ttl is the duration that an auth token will be good for (in seconds). The main package documentation uses 3600 (1 hour) by default. You might want to try 604800 (1 week) instead -- at least during testing.
You now need to create the PasswordGrantVerifier class and verify method that you just called in the code section above. So you create a file App/PasswordGrantVerifier.php and use the following code (which is basically what used to go in the closure for callback).
<?php
namespace App;
use Illuminate\Support\Facades\Auth;
class PasswordGrantVerifier
{
public function verify($username, $password)
{
$credentials = [
'email' => $username,
'password' => $password,
];
if (Auth::once($credentials)) {
return Auth::user()->id;
}
return false;
}
}
You will need at least one row in the oauth_clients table before OAuth2 will work. You can insert something manually or create a seeder. To create a seeder, modify database/seeds/DatabaseSeeder.php and add the following to the end of the run() method:
$this->call(OAuthClientsTableSeeder::class);
Now create a file called database/seeds/OAuthClientsTableSeeder.php and enter something like this:
<?php
use Illuminate\Database\Seeder;
class OAuthClientsTableSeeder extends Seeder
{
/**
* Run the database seeds.
*
* #return void
*/
public function run()
{
//Add sample users
$oAuthClients = array(
array(
'id' => 'TEST_ENVIRONMENT',
'secret' => 'b17b0ec30dbb6e1726a17972afad008be6a3e4a5',
'name' => 'TEST_ENVIRONMENT'
)
);
foreach ($oAuthClients as $oAuthClient) {
App\OAuthClient::create($oAuthClient);
}
}
}
Run php artisan vendor:publish to publish the package configuration and migrations. Run php artisan migrate to set up the billion-or-so new tables for OAuth. Run php artisan db:seed to seed your database.
You can now set up some test routes in app\Http\routes.php. They should look something like this:
Route::post('oauth/access_token', function() {
return Response::json(Authorizer::issueAccessToken());
});
Route::group(['middleware' => 'oauth'], function () {
Route::get('authroute', function() {
//OAuth will be required to access this route
});
Route::post('postwithauth', function(Request $request) {
$userID = Authorizer::getResourceOwnerId();
$input = $request->input();
return response()->json(array('userID' => $userID, 'input' => $input));
});
});
Route::get('noauthroute', function () {
//No authorization will be required to access this route
});
Pay close attention to the postwithauth route I included above. The OAuth2 package recently changed how you access the user's ID and it took me quite a while to figure out how to get it.
Now that it's time for testing, point your browser to localhost:8000 (or whatever the path is for your test environment) and create a user account for yourself (this step just uses the standard Laravel Auth package).
Go into your HTTP client (I'm currently using Paw and I like it). Go to request->authorization->OAuth2 to set up authorization for the route you're going to test. For Grant Type, select Resource Owner Password Credentials. If you used the seed example I provided above, the Client ID is TEST_ENVIRONMENT, the Client Secret is b17b0ec30dbb6e1726a17972afad008be6a3e4a5, enter the username (email) and password you created through the web Auth interface, your Access Toekn URL will be something like localhost:8000/oauth/access_token (depending on how you set up your test environment), leave Scope blank, and Token should say Bearer. Click on Get Access Token then say Use Access Token when prompted.
That should be it!
I am trying to implement Facebook Login to my project however I receive an error:
ClientException in Middleware.php line 69:
Client error: 400
in /Applications/MAMP/htdocs/laratest/vendor/guzzlehttp/guzzle/src/Middleware.php line 69
at Middleware::GuzzleHttp{closure}(object(Response)) in Promise.php line 199
at Promise::callHandler('1', object(Response), array(object(Promise), object(Closure), null)) in Promise.php line 152
at Promise::GuzzleHttp\Promise{closure}() in TaskQueue.php line 60
at TaskQueue->run() in CurlMultiHandler.php line 96
Steps I've gone through:
composer require laravel/socialite & composer update.
In my config>services.app,
'facebook' => [
'client_id' => env('FB_CLIENT_ID'),
'client_secret' => env('FB_SECRET_ID'),
'redirect' => 'http://localhost.com:8888',
],
Added
Laravel\Socialite\SocialiteServiceProvider::class, & 'Socialite' => Laravel\Socialite\Facades\Socialite::class, in config>app.php
Set up the routes successfully.
Route::get('auth/facebook', 'Auth\AuthController#redirectToProvider');
Route::get('auth/facebook', 'Auth\AuthController#handleProviderCallback');
Setup the link successfully in my blade file. Login with Facebook
In my Controller>AuthController.php, I added:
use Laravel\Socialite\Facades\Socialite;
** Beside everything that AuthController has, inside the AuthController class, I added:**
public function redirectToProvider()
{
return Socialite::driver('facebook')
->scopes(['scope1', 'scope2'])->redirect();
}
/**
* Obtain the user information from Facebook.
*
* #return Response
*/
public function handleProviderCallback()
{
$user = Socialite::driver('facebook')->user();
// $user->token;
}
Also users table:
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->increments('id');
$table->string('username');
$table->string('name');
$table->string('email')->unique();
$table->string('password', 60);
$table->string('avatar');
$table->string('provider');
$table->string('provider_id');
$table->rememberToken();
$table->timestamps();
});
}
Edit:
When I comment out $user = Socialite::driver('facebook')->user(); part, I get redirected to localhost.com/auth/facebook
Edit 2:
My .env file:
'facebook' => [
FB_CLIENT_ID => '###',
FB_SECRET_ID => 'this-is-secret!',
],
I think your two routes should not have the same URL, because Laravel has no way to know which to use.
Facebook returns a ?code parameter, so make use of it! If ?code is included, then handle the login logic from Socialite, if not, redirect to Facebook.
You have two unknown scopes in your code, scope1 and scope2, those are unknown to Facebook and will return an error when trying to access them. Use real scopes like public_profile or email and you should be set. (scopes are the permissions you are requesting)
Make sure Laravel is calling properly your config entries with
php artisan config:clear
And also check your .env file for FB_CLIENT_ID and FB_SECRET_ID entries. Probably client's failing because it's trying to connect to facebook without client and secret, falling into an exception.
I've had the same problem(ClientException in Middleware.php line 69: Client error: 400).
I just switch off "Require App Secret " in the facebook's advanced settings and this solved the problem. Maybe this information will be helpful.
I had a similar problem with Facebook APi - it gave Response code 400
and Ive just tried:
composer require laravel/socialite & composer update
and it updated dependencis:
Updating dependencies (including require-dev)
- Removing laravel/socialite (v2.0.11)
- Installing laravel/socialite (v2.0.14)
Im Using Laravel 5.1:
composer.json:
"laravel/framework": "5.1.*",
"laravel/socialite": "^2.0"
And it seems to work fine now
Hope this helps someone