I'm using Laravel Passport to issue Bearers to the user once it's logged in and then be able to connect to the API (yes I know Sanctum might fit better my needs but I'm using Passport so don't even mention to switch to Sanctum), then on the Front-end, I store the user email and the Bearer in a cookie to use them later on for other queries and add the bearer to Axios Auth header though now I have a problem with my logic, maybe related to the fact that I don't know how to use Passport correctly in Nuxt.
I have queries for each page where I send the user email in a post request and they return back a mix of global info and user info.
Yes, my endpoints are already behind an auth middleware but I just send a Bearer Token to allow the endpoint to be queried with any data, there is no prevention to ask for User B info from User A.
How can I use prevent the user to send a different email and get another user's info?
This is how I issue an access token:
$token = $user->createToken('Laravel Password Grant Client')->accessToken;
there is a way to do something like this?
$user = User::where('email', $request->email)->first();
// E.g. user#mail.com ...
// Get the user Access Token
$userToken = $user->getAccessToken;
// E.g. someBerareText
// check if the User Access Token match with the one send in the request
// if they don't match throw a 401
if ($userToken !== $request->header('Authorization')) {
return response()->json([ "error" => "Not Authorized" ], 401);
...
// E.g. $request->header('Authorization') it's SomeOtherBearer because he
// requested info for user#mail.com but the $request->header('Authorization')
// belong to otheruser#othermail.com
The user can still send the same request but with User B's email and see other info that doesn't belong to him, so how can I check if the email in the $request belongs to the user that's actually logged in?
some way to decode the access token and check if it really belongs to the user or not?
If Laravel Passport is set up as the guard, it will fetch the user from the bearer token, the logic can be seen in the TokenGuard.php class in Passport. It actually does the same as you want to achieve.
So the authentication works different compared to the guard used. Therefor Passport requires you to change the guard. This is the deciding factor how Laravel differentiate Authentication and for that matter how the Auth::user() is loaded.
'guards' => [
...
'api' => [
'driver' => 'passport',
'provider' => 'users',
],
],
This means that you can check if the user is the correct authenticated with the model function is() that compare if the models are the same.
User::where('email', $request->email)->first()->is(Auth::user())
Related
In my laravel project I use Laravel Passport Password and Client Credentials Grant Tokens.
Client Credentials Grant Tokens
The client credentials grant is suitable for machine-to-machine authentication. For example, you might use this grant in a scheduled job which is performing maintenance tasks over an API.
This grant type has middleware for verify client credentials.
Middleware path
Laravel\Passport\Http\Middleware\CheckClientCredentials
By registering this middleware in app/Http/Kernel.php we can protect our routes
use Laravel\Passport\Http\Middleware\CheckClientCredentials;
protected $routeMiddleware = [
'client' => CheckClientCredentials::class,
];
Example route protecting
Route::get('/orders', function (Request $request) {
...
})->middleware('client');
We can get any grant type access tokens by requesting to: http://your-app.com/oauth/token
Example
$guzzle = new GuzzleHttp\Client;
$response = $guzzle->post('http://your-app.com/oauth/token', [
'form_params' => [
'grant_type' => 'client_credentials',
'client_id' => 'client-id',
'client_secret' => 'client-secret',
'scope' => 'your-scope',
],
]);
return json_decode((string) $response->getBody(), true)['access_token'];
Password Grant Tokens
The OAuth2 password grant allows your other first-party clients, such as a mobile application, to obtain an access token using an e-mail address / username and password. This allows you to issue access tokens securely to your first-party clients without requiring your users to go through the entire OAuth2 authorization code redirect flow.
Whith Password Grant Client we can authorize our users and protect auhorized routes using auth:api middleware. For eccess authorized routes we must set token to request header to access_token field.
Example route protecting
Route::get('/orders', function (Request $request) {
...
})->middleware('auth:api');
Now when both grant types (Password Grant Tokens && Client Credentials Grant Tokens) need to access token from header field access_token how I can protect my routes?
Something like this
Route::get('/orders', function (Request $request) {
...
})->middleware(['client', 'auth:api']);
How to organize such protection using Laravel Passport when both middleware waiting token from one field access_token of header?
like this maybe ??
Route::middleware(['client', 'auth:api'])->group(function(){
Route::get(....);
});
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.
I'm trying to implement token-based authentication in Laravel 5.5 using Tymon's JWTAuth. I followed the GitHub Documentation for the library and am using the following authentication flow. Here is the authentication portion of my login route:
try {
// attempt to verify the credentials and create a token for the user
if (!$token = JWTAuth::attempt($credentials)) {
return response()->json(['success' => false, 'error' => 'Invalid Credentials. Please make sure you entered the right information and you have verified your email address.'], 401);
}
}
catch (JWTException $e) {
// something went wrong whilst attempting to encode the token
return response()->json(['success' => false, 'error' => 'could_not_create_token'], 500);
}
// all good so return the token
return response()->json(['success' => true, 'data'=> [ 'token' => $token ]]);
And here are the routes:
Route::group([
'middleware' => ['jwt.auth', 'jwt.refresh'],
],
function () {
// Routes requiring authentication
Route::get('/logout', 'Auth\LoginController#logout');
Route::get('/protected', function() {
return 'This is a protected page. You must be logged in to see it.';
});
});
So you can see I am using the jwt.auth and jwt.refresh middlewares. Now, everything is seemingly working as expected and I can authenticate users with tokens. Each token has a lifespan of one use and I am provided another valid token after each request (the refresh flow).
However, my problem is that if I have a valid token for a user that has not been used yet, and then I remove it from the header and hit the /login route with valid credentials, I am issued another valid token. So now I have two valid tokens that can be used to authenticate a user, as my /login route is not invalidating previously issued tokens.
Does anyone know of a way to check to see if a user has an outstanding valid token, so that it can be invalidated if the user logs in from elsewhere?
I'll answer my own question after doing some research. From my understanding, JWT tokens are valid unless explicitly blacklisted. As long as the server recognizes that it itself created the token, then it can decipher the token using a secret key and assume that it's valid. That's why token lifetimes are so important. So if you want to invalidate an issued token, you'd either have to wait for expiry or blacklist it.
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
I need to write a very specific authentication for my web application. There is API on the side which accepts login + password pair and returns the result (and, a token). I don't want to store any login information on the Yii2 side besides a login token i've got from API. And this must be the only way i auth my clients (so i don't use OAuth-like application).
What is the best practive to override "classic" code in Yii2? Just use filters and modify User model?
Example:
First, i recieve a token and save it somewhere for a session:
$token = GatewayAPI::login($user, $password);
Then, every internal request i do will look like this:
$result = GatewayAPI::addPosition($token, $data);
So, i don't have any database to work with, just cache and memory. Almost everything is handled on API side.
My task is to implement login check - if token is recieved from API - then it's considered as a success. And to store that token for use within current session (probably in memcache, it must not be opened to public).
As a matter of fact Yii2 does not require login/password anywhere.
You don't need to modify or extend User model if you mean \yii\web\User.
You need to create your own class implementing IdentityInterface and set this class as userIdentity in your config components->user->identityClass:
[
'components' => [
'user' => [
'class' => 'yii\web\User', // not necessary, this is by default
'identityClass' => 'my\namespace\User'
]
]
]
There are 5 methods in the interface and they are not about login/pass. This class of yours may store in your db everything you want.
For example you may copy any of popular user modules to your project, remove everything related to storing and searching by login/pass from that User model and add your API functionality - and it will work.
UPD.
Your added functionality will look like this:
$token = GatewayAPI::login($user, $password);
$user = \my\namespace\User::findOne(['token' => $token]);
Yii::$app->user->login($user);