Yii Framework get user data - php

How can I get currently online user data with all the parameters from the database? Right now I get it's ID by using this:
echo Yii::$app->user->getId();
Can I reach other data somehow or do I have to create a function which gets all the information by the user ID?

You can access to all the identity values in this way
this for username
Yii::$app->user->identity->username
check for your User models for others attributes
http://www.yiiframework.com/doc-2.0/guide-security-authentication.html
http://www.yiiframework.com/doc-2.0/yii-web-identityinterface.html
http://www.yiiframework.com/doc-2.0/yii-web-user.html
(and your User model of course)

You can access the model of the currently logged in user with:
$user = Yii::$app->user->identity;
This will return either null (if the user is not logged in) or an instance of the identityClass you defined for the user component in your config. Ex:
'user' => [
'identityClass' => 'app\models\User',
'enableAutoLogin' => true,
],
So you can use it like any other model class and access it's attributes and/or methods. Just make sure you configure correctly the user component and the identityClass exists and can be accessed.

current user is:
Yii::$app->user->identity
it can be null, so check it before accessing fields

Related

Laravel Passport get user Access Token after being issued

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())

Share Laravel Session on Subdomains with different guards

I'm currently using spatie/permissions and a Subclass of User with constraints to permissions to Login to subdomains in my application.
I now want to be able to share the session between my main domain example.com and the domains some.example.com where some is dynamically loaded from database.
When my logged-in User in example.com accesses the abc.example.com domain and is able to log in there I want to use the current session.
I use different guards on subdomain and domain with the SubUser and User classes as providers.
I already use the database session driver and can see in the logs that the same session id is loaded from database.
As the application is loading the same session from database I'm wondering why my user is not already logged in.
Anyone ever tried this and got a solution for this?
So I managed to resolve this issue.
My setup is all subdomains got the user guard and the main domain has the admin guard.
I realised that the Auth::getName() included the guard name and as I logged in using different guards I ended up having two active logins in one session. But these logins had different names and where only valid with the right guard. This guard being different in main domain and subdomains resulted in not really sharing login-state over domain and subdomains.
I managed to resolve this by overriding the default laravel SessionGuard and adding my own driver like so:
In config/auth.php:
'guards' => [
'user' => [
'driver' => 'extended_session',
'provider' => 'users',
],
'admin' => [
'driver' => 'extended_session',
'provider' => 'admins',
],
]
In AppServiceProvider.php
\Auth::extend('extended_session', function ($app, $name, $config) {
$providerConfig = $this->app['config']['auth.providers.'.$config['provider']];
// If you don't use eloquent you need to alter the next line accordingly
$provider = new EloquentUserProvider($app['hash'], $providerConfig['model']);
return new SessionGuardExtended('extended_session', $provider, $this->app['session.store']);
});
And add a new Class named SessionGuardExtended like this:
use Illuminate\Auth\SessionGuard;
class SessionGuardExtended extends SessionGuard{}
This results in a shared session with the same auth name for domain and subdomains.
Add SESSION_DOMAIN to your .env file and set it to .example.com

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

Best practice of writing custom authentication mechanism on Yii2

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);

Lithium all rows in cookies, session

Currently after login Lithium stores in session and cookies all rows from users table like password, hash etc. How to remove (don't allow to store) some of the information like password and hash?
The Session class stores what you tell it to! After Auth::check is done, you should only store the session identifier and/or absolutely necessary data in the cookie. Also make sure to use the Encryption provided by lithium (AES) out of the box.
For more detailed help, please post your login controller and all appropriate model/filters.
Passing options to Auth::check will get passed down to the adapter as well (plus some extras) -- for this I'm assuming you're using the Form adapter for the Auth class.
Try doing this when you perform your check: Auth::check('config', $data, array('fields' => array('fields', 'you', 'want'))
The key here is that array we tacked on the end with the fields key in it, this will be passed down to the Form adapter which takes in those options and uses them to query your model for a matching user. By telling it explicitly which fields to return, it will only pass those back to the Auth class for storing away.
Since this commit you can pass an option 'persist' => array('field1','..') to Auth::check, or set them as default in your bootstrap session config, to store only specified fields.
So either you set this in your bootstrap/session.php
Auth::config(array(
'user' => array(
'adapter' => 'Form',
'session' => array(
'persist' => array('_id','username')
),
'model' => 'Users'
)
));
or you define the fields, when calling Auth::check() - this will override everything from the config above!
Auth::check('user', $this->request, array(
'persist' => array('username','email')
))
Note: If not defined explicitly the password is never stored by default.

Categories