PHP Slim with Firebase JWT - php

I am trying to integrate Firebase Auth with PHP Slim (JWT) without any luck. I login using my firebase user and save my token correctly. Then I set my midleware.php like this:
$app->add(new Tuupola\Middleware\JwtAuthentication([
"ignore" => ["/countries","/faqs"],
"secret" => $secrets,
"secure" => false
]));
where $secrets is the kid coming from securetoken#system.gserviceaccount.com. However I keep getting an error 401 not authorized.
Same code works when I try it with a custom $secret and custom jwt. Does Firebase need something extra in the JwtAuthentication?

So the issue was how JwtAuthentication from Tuupola handles the kid (it is actually kid - key id which comes from googles public keys).
In vanilla PHP, I had to get the secrets from google, then split it into arrays before using it. However, this is all done by Tuupola internally, which caused my issue.
The correct Middleware for Slim 4 to work with Firebase Auth is the following:
return function (App $app) {
$app->add(CorsMiddleware::class);
$container = $app->getContainer();
$rawPublicKeys = file_get_contents('https://www.googleapis.com/robot/v1/metadata/x509/securetoken#system.gserviceaccount.com');
$keys = json_decode($rawPublicKeys, true);
$app->add(new Tuupola\Middleware\JwtAuthentication([
"ignore" => ["/api/records/countries","/faqs","/","/answer"],
"algorithm" => ["RS256"],
"header" => "X-Authorization",
"regexp" => "/Bearer\s+(.*)$/i",
"secret" => $keys,
"secure" => false
}
]));
};

Related

How to use Laravel Passport with Password Grant Tokens?

I just read the https://laravel.com/docs/5.6/passport documentation and I have some doubts that hopefully someone could help me with:
First, some context, I want to use Passport as a way to provide Oauth authentication for my mobile app (first-party app).
When I use php artisan passport:client --password I get back a Client ID and a Client Secret. Does this value have to be fixed on my app? for example storing them hardcoded or as a "settings" file? If the values shouldn't be stored then how should it work?
To register a user to my app I use: $user->createToken('The-App')->accessToken; I get that the accessToken will be the one used for sending on all my requests as a Header (Authorization => Bearer $accessToken) but what exactly is "The-App" value for?
For login the user I'm using the URL: http://example.com/oauth/token and sending as parameters:
{
"username": "user#email.com",
"password": "userpassword",
"grant_type": "password",
"client_id": 1, // The Client ID that I got from the command (question 1)
"client_secret": "Shhh" // The Client Secret that I got from the command (question 1)
}
When I login the user using the previous endpoint I get back a refresh_token, I read that I could refresh the token through http://example.com/oauth/token/refresh but I try to request the refresh I got Error 419, I removed the url oauth/token/refresh from the csrf verification and now I get back "message": "Unauthenticated.", I'm making the following request:
Content-Type: x-www-form-urlencoded
grant_type: refresh_token
refresh_token: the-refresh-token // The Refresh Token that I got from the command (question 3)
client_id: 1 // The Client ID that I got from the command (question 1)
client_secret: Shhh // The Client Secret that I got from the command (question 1)
scope: ''
Should I use this endpoint? or is not necessary given the app I'm trying to develop.
Finally, there are a lot of endpoints that I get from passport that I don't think I will use for example: oauth/clients*, oauth/personal-access-tokens* is there a way to remove them from the endpoints published by passport?
Thanks a lot for your help!
If you are consuming your own api then you don't need to call http://example.com/oauth/token
for user login because then you need to store client_id and client_secret at app side. Better you create an api for login and there you can check the credentials and generate the personal token.
public function login(Request $request)
{
$credentials = $request->only('email', 'password');
if (Auth::attempt($credentials)) {
// Authentication passed...
$user = Auth::user();
$token = $user->createToken('Token Name')->accessToken;
return response()->json($token);
}
}
Finally, there are a lot of endpoints that I get from passport that I
don't think I will use for example: oauth/clients*,
oauth/personal-access-tokens* is there a way to remove them from the
endpoints published by passport?
You need to remove Passport::routes(); from AuthServiceProvider and manually put only required passport routes. I think you only need oauth/token route.
what exactly is "The-App" value for?
if you check oauth_access_tokens table it has name field. $user->createToken('Token Name')->accessToken; here the "Token Name" stored in name field.
How to use Laravel Passport with Password Grant Tokens?
To generate password grant token you have to store client_id and client_secret at app side (not recommended, check this ) and suppose if you have to reset the client_secret then the old version app stop working, these are the problems. To generate password grant token you have to call this api like you mention in step 3.
$http = new GuzzleHttp\Client;
$response = $http->post('http://your-app.com/oauth/token', [
'form_params' => [
'grant_type' => 'password',
'client_id' => 'client-id',
'client_secret' => 'client-secret',
'username' => 'taylor#laravel.com',
'password' => 'my-password',
'scope' => '',
],
]);
return json_decode((string) $response->getBody(), true);
Generate token from refresh_token
$http = new GuzzleHttp\Client;
$response = $http->post('http://your-app.com/oauth/token', [
'form_params' => [
'grant_type' => 'refresh_token',
'refresh_token' => 'the-refresh-token',
'client_id' => 'client-id',
'client_secret' => 'client-secret',
'scope' => '',
],
]);
return json_decode((string) $response->getBody(), true);
You can look this https://laravel.com/docs/5.6/passport#implicit-grant-tokens too.
Tackling Question 5
Finally, there are a lot of endpoints that I get from passport that I don't think I will use for example: oauth/clients*, oauth/personal-access-tokens* is there a way to remove them from the endpoints published by passport?
Passport::routes($callback = null, array $options = []) takes an optional $callback function and optional $options argument.
The callback function takes a $router argument from which you can then choose which routes to install as shown below in your AuthServiceProvider.php that is enabling a more granular configuration:
Passport::routes(function ($router) {
$router->forAccessTokens();
$router->forPersonalAccessTokens();
$router->forTransientTokens();
});
Passport::tokensExpireIn(Carbon::now()->addMinutes(10));
Passport::refreshTokensExpireIn(Carbon::now()->addDays(10));
This way we only create the passport routes that we need.
forAccessTokens(); enable us to create access tokens.
forPersonalAccessTokens(); enable us to create personal tokens although we will not use this in this article. Lastly,
forTransientTokens(); creates the route for refreshing tokens.
If you run php artisan route:list you can see the new endpoints installed by Laravel Passport.
| POST | oauth/token | \Laravel\Passport\Http\Controllers\AccessTokenController#issueToken
| POST | oauth/token/refresh | \Laravel\Passport\Http\Controllers\TransientTokenController#refresh

Can't get Auth object and cookies by consuming my own Laravel API

I'm currently trying to build a secure SPA application in Laravel by using :
Laravel 5.6
Laravel Passport
Guzzle client
To make the whole application secure, I created a proxy to prefix all requests to the API and :
User the password grand type of token
Hide the client ID
Hide the client secret
Add automatic scopes based on the role of the user
This is how the Proxy works :
// The proxify endpoint for all API requests
Route::group(['middleware' => ['web']], function ()
{
Route::any('proxify/{url?}', function(Request $request, $url) {
return Proxify::makeRequest($request->method(), $request->all(), $url);
})->where('url', '(.*)');
});
Each time a request is made, it goes through that package I built to create the access token, refreshing it, or deleting it.
To create the access token for the user I'm using a MiddleWare at loggin :
$response = $http->post('http://myproject.local/proxify/oauth/token', [
'form_params' => [
'grant_type' => 'password',
'username' => $request->get('email'),
'password' => $request->get('password'),
]
]);
This is working well, excepting the fact that I'm setting cookies in the Proxify::makeRequest, so I have to create them in the call, return them in the $response, and then at the end of the Middleware, attaching them to the request (Cookie::queue and Cookie::Make are not working in a Guzzle call it seems).
The access token is created and stored in a cookie.
First problem is that in this call, even in the middleware, and especially in that URL http://myproject.local/proxify/oauth/token, I don't have any access to the Auth trait, even if it's specified as a middleware attached to the route, so impossible to fetch information from the authenticated user.
Then the other problem is that when I'm making a call to get a ressource API such as :
$http = new Client();
$response = $http->get('http://myproject.local/proxify/api/continents');
$continents = $response->getBody()->getContents();
return view('dashboard')->with("continents", $continents);
In that case, when I'm calling the URL, the proxy is not able to get the access_token defined in the cookie with the CookieFacade through the HTTP call, neither the Auth object I'm whiling to use. The $_COOKIE variable is not working neither.
What is wrong with my structure so that I don't have any access to the cookie even if it's set and in the browser ? Any other way to get the info ? I tried to get the cookie from the request in the proxy, not working.
Have you tried using the Illuminate or Symfony Request classes and handling the routing via the Laravel instance? My immediate suspicion is Guzzle is the culprit behind no cookies coming through with the requests. Cookie::queue() is a Laravel specific feature so I wouldn't think Guzzle would know anything about them.
Replace Guzzle in one of the routes where the issue occurs. Start with a new Request instance and make the internal api call like:
// create new Illuminate request
$request = Request::create('/api/users', $action, $data, [], [], [
'Accept' => 'application/json',
]);
// let the application instance handle the request
$response = app()->handle($request);
// return the Illuminate response with cookies
return $response->withCookies($myCookies);
I do something similar to this in a couple applications and never had any problems with cookies or accessing server variables. Granted, it's only for authentication, the rest of the api calls are through axios.

How to limit user actions with Laravel Passport Scopes + Password Grant Type

I have set up the Laravel Passport package for Laravel 5.3 just as described in the official documentation (https://laravel.com/docs/5.3/passport#introduction).
I want the API to be consumed by a mobile application, so I am trying to implement Password Grant Tokens. I have created a password grant client, and the token request process...
$response = $http->post('http://my-app.com/oauth/token', [
'form_params' => [
'grant_type' => 'password',
'client_id' => 'client-id',
'client_secret' => 'client-secret',
'username' => 'my#email.com',
'password' => 'my-password',
'scope' => '',
],
]);
...Just works as expected, returning an access-token and a refresh-token for one of my users.
But now I want to define some scopes so I can limit the access of users... Following the documentation again, I have them defined in boot method of AuthServiceProvider.php like:
Passport::tokensCan([
'admin' => 'Perform every action',
'user' => 'Perform only normal user actions',
]);
In this scenario, if a "malicious" normal user requested a token (using the above POST call) specifying 'scope' => 'admin', he or she would get an 'admin' token... and that is not what I want.
Thus, I would like to know how is the workflow in this situation to effectively limit the access to normal users, and where do I have to implement the scope validation logic.
Thanks in advance.
One way to go about this would be to create a middleware
For example if you only want users with an email from example.com to request the admin domain you can do something like this
Example ScopeLogic.php middleware:
if ($request->input('grant_type') === 'password') {
$scope = $request->input('scope');
$username = $request->input('username');
if ($scope === 'admin' && (strpos($username, '#example.com') === false)) {
return response()->json(['message' => "Not authorized to request admin scope"], 401);
}
}
return $next($request);
Of course, you would have to add this scope to your $routeMiddleware array in Kernel.php
protected $routeMiddleware = [
...
'check-scopes' => \App\Http\Middleware\ScopeLogic::class
]
As well as wrap Passport::routes() in AuthServiceProvider.php to check for this middleware
\Route::group(['middleware' => 'check-scopes'], function() {
Passport::routes();
});
Passport will also check that a correct username and passport combination was passed so you don't have to worry about that in the middleware
In my opinion, I think what confuses most people with OAuth and APIs is that scopes are tied to "clients" and not the "resource owner" themselves. Clients should be able to talk to an API using an admin scope or no scopes at all if needed. If they use an admin-ish type scope together with user context (password grant, authorization code grant, etc), then there is no stopping them from making calls that require such a scope against that user in the API. To me, the only person that can truly be classified as malicious would be one who manages to steal an access token containing an admin scope. That is why API implementors are allowed to specify what scopes to grant a client and if it's a first party app that uses something like the Password Grant, then you as a user has no choice but to trust it with your data.
I don't know how one would do this and use the retrieved token inside another's mobile app but if you did try requesting a token manually yourself with an admin scope, then I really don't see anything wrong that (other than you giving the app more control with you set as user context, so it may even be counter productive?)
If you need more control than that, then you need to go past your API and create something like application-level permissions for each user inside your resource server.
I forget where I read it, some Github issues somewhere, but apparently Laravel doesn't have that ability built in, each client is the treated the same equally, out of the box.
A user provided a good solution, and I built upon it here: https://stackoverflow.com/a/55285483/1132557

Getting Google Refresh Token with Laravel Socialite

Using Laravel Socailite to connect to the Google API and I am getting back a connection fine, however this access doesn't return a refresh token so my connection is timing out.
$scopes = [
'https://www.googleapis.com/auth/webmasters',
'https://www.googleapis.com/auth/webmasters.readonly',
'https://www.googleapis.com/auth/analytics.readonly',
'https://www.googleapis.com/auth/userinfo.profile',
'https://www.googleapis.com/auth/userinfo.email',
];
$parameters = ['access_type' => 'offline'];
return Socialite::driver('google')->scopes($scopes)->with($parameters)->redirect();
How do I get the refresh token back?
When you redirect your users to Google, set access_type to offline with the with() method when redirecting, like this:
return Socialite::driver('google')
->scopes() // For any extra scopes you need, see https://developers.google.com/identity/protocols/googlescopes for a full list; alternatively use constants shipped with Google's PHP Client Library
->with(["access_type" => "offline", "prompt" => "consent select_account"])
->redirect();

Globally accessible user object in controllers and models

I'm building a Laravel API which authenticates users using an authentication token. For any routes that need authentication, I'm wrapping them in an auth filter:
Route::group(array('before' => 'auth'), function() {
Route::get('user/account', 'UserController#getAccountDetails');
});
My auth filter basically decrypts the passed in authentication token and checks if it's valid:
Route::filter('auth', function()
{
// Try catch because Crypt::decrypt throws an exception if it's not a valid string to decrypt
try {
$authToken = Crypt::decrypt(Request::header('Authorization'));
// If there's a user tied to this auth token, it's valid
$user = AuthToken::where('token', '=', $authToken)->first()->user()->first();
if (!$user) {
throw new \Exception();
}
// Make the user globally accessible in controllers
} catch (\Exception $e) {
return Response::json([
'data' => [
'error' => 'You must be logged in to access this resource.'
],
'success' => false,
'status' => 403
], 403);
}
});
Pretty simple stuff, but I'm stuck on the next part. I want to be able to easily retrieve the current user record in my controllers and models.
For example, if I used Laravel's Auth library I could get the current user by doing Auth::user() in my controllers. I'd like to have that kind of functionality but I'm not sure how to build it. Could I write a class that gets instantiated after authentication with a static method that returns a User model?
Not sure if that's an option for you, but maybe you would like to use oauth2 instead of writing "your own" token based authentication?
There is quite nice ouath2 server wrapper for laravel project: oauth2-server-laravel.
According to it's documentation you can (for example for password flow authentication) put this in it's config:
'password' => array(
'class' => 'League\OAuth2\Server\Grant\Password',
'access_token_ttl' => 604800,
'callback' => function($username, $password){
$credentials = array(
'email' => $username,
'password' => $password,
);
$valid = Auth::validate($credentials);
if (!$valid) {
return false;
}
return Auth::getProvider()->retrieveByCredentials($credentials)->id;
}
)
And than you can you can authenticate (via username and password in that case) sending post request like that:
POST https://www.example.com/oauth/access_token?
grant_type=password&
client_id=the_client_id&
client_secret=the_client_secret&
username=the_username&
password=the_password&
scope=scope1,scope2&
state=123456789
Request will return generated token, and then you can make api calls as usual, just putting the token in the post data.
In your api logic getting the user by token is quite simple in that case, just run:
User::find(ResourceServer::getOwnerId());
It will makes stuff like: refresh tokens, other grant flows, scope access, clients management a lot easier. Out of the box in fact.
You can also secure any particular route like that:
Route::get('secure-route', array('before' => 'oauth', function(){
return "oauth secured route";
}));
You can find more details in oauth2-server-laravel documentation: https://github.com/lucadegasperi/oauth2-server-laravel
And oauth2 documentation: http://oauth.net/documentation/
It is true that the Auth::user() method is quite convenient. So, why not simply extend the Auth class to write your own authentication driver ? You can find all needed doc here.
You can then just use the Auth facade just like in every other laravel app you could write… wonderful, isn't it ?

Categories