Updating Zend_Auth storage after editing a user - zend-framework

I use Zend_Auth to authenticate users and then store their details in the default Zend_Auth session. This means that when a user edits his details, these changes won't be reflected in the application until he re-authenticates.
I want to avoid this problem as so:
When the user logs in we only store his user ID in a Zend_Auth session
On each request we fetch the user's details from the database in a preDispatch() hook, using the user ID which was stored upon login in the Zend_Auth session:
class Plugin_Auth extends Zend_Controller_Plugin_Abstract
{
public function preDispatch(Zend_Controller_Request_Abstract $request)
{
if ($auth->hasIdentity())
{
$id = $auth->getIdentity()->id;
$userModel = new Model_User();
$user = $userModel->fetchOne($id);
// Where do I store this user object ???
}
}
}
The problem is: where do i store this User object? I think we shouldn't use sessions for this, since the goal of sessions is to persist data. There's no need for persistence though, since we re-fetch the data from the database on each request. Only the user ID must be persistent. Would storing the User object in Zend_Registry be an option here?

I think example would be the best to explain how to write new auth details to Zend_Auth storage object:
$userDetails = array('foo' => 'bar');
$storage = new Zend_Auth_Storage_Session();
// set sorage for Zend_Auth
Zend_Auth::getInstance()->setStorage($storage);
// write data to the storage
Zend_Auth::getInstance()->getStorage()->write($userDetails);
// read data from storage
var_dump(Zend_Auth::getInstance()->getStorage()->read());
// edit user's data
$userDetails = array('foo' => 'bar', 'foo', 'bar');
// write new data to storage
Zend_Auth::getInstance()->getStorage()->write($userDetails);
// read new written data from storage
var_dump(Zend_Auth::getInstance()->getStorage()->read());
I think this explains how to set Zend_Auth storage, and change it later.

Use Zend_Session_Namespace to store the object. It can be as temporary or permanent as you wish to make it.
Zend_Auth already uses this in the background as it's default storage mechanism using the namespace of Zend_Auth.
class Plugin_Auth extends Zend_Controller_Plugin_Abstract
{
public function preDispatch(Zend_Controller_Request_Abstract $request)
{
$session = new Zend_Session_Namespace('user');//instantiate session namespace
if ($auth->hasIdentity())
{
$id = $auth->getIdentity()->id;
$userModel = new Model_User();
$user = $userModel->fetchOne($id);
$session->user = $user;//store the object can be recalled anywhere
}
}
}
Of course Zend_Registry will work as well and as always the choice is yours. You may even find it appropriate to build this functionality into your auth adapter.

I believe using Zend_Registry is fine in your case.

Related

How do I loosely couple the Blazor Identity scaffold with my own Database Context?

I've created a Blazor Server App with the option to scaffold an identity system. This created an Entity Framework IdentityDbContext with a number of tables to manage user logins and settings. I decided to keep my own DbContext separate from this so that I could replace either of the contexts later, if necessary.
What I would like to do is have a User entity in my own custom dbcontext, and in it store a reference to the user id of the scaffolded IdentityDbContext entity. I would also like to ensure that I don't have to query the db for the custom entity every time the user opens a new page.
I've been looking around StackOverflow trying to find good suggestions of how to approach this, but I'm still not sure how to start. So I have a few questions:
Is my approach a sensible one?
How do I find a permanent id number or string to couple with on the UserIdentity?
Should I store my custom user entity in some sort of context so I don't have to query it all the time? If so, how?
All help is greatly appreciated!
It looks like your requirement is to store custom information about the current user above and beyond what is stored in Identity about the current user.
For simpler use cases you can create your own User class derived from IdentityUser and add additional properties on there and let Identity take care of all persistence and retrieval.
For more complex use cases you may follow the approach you have taken, whereby you create your own tables to store user related information.
It seems that you have taken the second approach.
Is my approach a sensible one?
I think so. Burying lots of business-specific context about the user in the Identity tables would tightly bind you to the Identity implementation.
How do I find a permanent id number or string to couple with on the
UserIdentity?
IdentityUser user = await UserManager<IdentityUser>.FindByNameAsync(username);
string uniqueId = user.Id;
// or, if the user is signed in ...
string uniqueId = UserManager<IdentityUser>.GetUserId(HttpContext.User);
Should I store my custom user entity in some sort of context so I
don't have to query it all the time? If so, how?
Let's say you have a class structure from your own DbContext that stores custom information about the user, then you can retrieve that when the user signs in, serialize it, and put it in a claim on the ClaimsPrincipal. This will then be available to you with every request without going back to the database. You can deserialize it from the Claims collection as needed and use it as required.
How to ...
Create a CustomUserClaimsPrincipalFactory (this will add custom claims when the user is authenticated by retrieving data from ICustomUserInfoService and storing in claims):
public class CustomUserClaimsPrincipalFactory
: UserClaimsPrincipalFactory<ApplicationUser, IdentityRole>
{
private readonly ICustomUserInfoService _customUserInfoService;
public CustomUserClaimsPrincipalFactory(
UserManager<ApplicationUser> userManager,
RoleManager<IdentityRole> roleManager,
IOptions<IdentityOptions> optionsAccessor,
ICustomUserInfoService customUserInfoService)
: base(userManager, roleManager, optionsAccessor)
{
_customUserInfoService= customUserInfoService;
}
protected override async Task<ClaimsIdentity> GenerateClaimsAsync(
ApplicationUser user)
{
var identity = await base.GenerateClaimsAsync(user);
MyCustomUserInfo customUserInfo =
await _customUserInfoService.GetInfoAsync();
// NOTE:
// ... to add more claims, the claim type need to be registered
// ... in StartUp.cs : ConfigureServices
// e.g
//services.AddIdentityServer()
// .AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options =>
// {
// options.IdentityResources["openid"].UserClaims.Add("role");
// options.ApiResources.Single().UserClaims.Add("role");
// options.IdentityResources["openid"].UserClaims.Add("my-custom-info");
// options.ApiResources.Single().UserClaims.Add("my-custom-info");
// });
List<Claim> claims = new List<Claim>
{
// Add serialized custom user info to claims
new Claim("my-custom-info", JsonSerializer.Serialize(customUserInfo))
};
identity.AddClaims(claims.ToArray());
return identity;
}
}
Register your CustomUserInfoService in Startup.cs (your own service to get your custom user info from the database):
services.AddScoped<ICustomUserInfoService>(_ => new CustomUserInfoService());
Register Identity Options (with your CustomUserClaimsPrincipalFactory and authorisation in Startup.cs. NOTE: addition of "my-custom-info" as a registered userclaim type. Without this your code in CustomUserInfoService will fail to add the claim type "my-custom-info":
services.AddDefaultIdentity<IdentityUser>(options =>
{
options.SignIn.RequireConfirmedAccount = false;
options.User.RequireUniqueEmail = true;
})
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddClaimsPrincipalFactory<CustomUserClaimsPrincipalFactory>();
services.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options =>
{
options.IdentityResources["openid"].UserClaims.Add("role");
options.ApiResources.Single().UserClaims.Add("role");
options.IdentityResources["openid"].UserClaims.Add("my-custom-info");
options.ApiResources.Single().UserClaims.Add("my-custom-info");
});
You can then retrieve your custom user info from claims, without returning to database, by using:
MyCustomUserInfo customUserInfo =
JsonSerializer.Deserialize<MyCustomUserInfo>(
HttpContext.User.Claims
.SingleOrDefault(c => c.Type == "my-custom-info").Value);

REST Api with QueryParamAuth authenticator - Yii2

I'm trying to create rest api for my application to get the data in my android app. This is my controller
<?php
namespace api\modules\v1\controllers;
use yii\rest\ActiveController;
use yii\filters\auth\QueryParamAuth;
/**
* Tk103 Controller API
*/
class Tk103Controller extends ActiveController
{
public $modelClass = 'api\modules\v1\models\Tk103CurrentLocation';
public function behaviors()
{
$behaviors = parent::behaviors();
$behaviors['authenticator'] = [
'class' => QueryParamAuth::className(),
];
return $behaviors;
}
}
I added access_token column in my user table, implemented findIdentityByAccessToken() in User Model and calling this URL
http://localhost:7872/api/v1/tk103s?access-token=abcd
This is working great and returning data if and only if access_token matches with any single user in the table.
I checked QueryParamAuth class and found that QueryParamAuth::authenticate() returns $identity after successful authentication.
Currently this url is returning whole data of my table.
What I want is(after authentication):
Get user id/username of the requester.
Based on that id/username, the data related to him as per relations of tables in db. (currently whole rows are being returned but I want only few that are matching with the current requester/user)
I tried but didn't getting any clue to catch returned $identity of user after authentication.
And I know it is possible too to make this work. Help me out folks to create magic.
Get user id/username of the requester.
That user instance you did return within the findIdentityByAccessToken method should be accessible any where inside your app within Yii::$app->user->identity. And should hold all the attributes retreived from DB. here is a quick example of using it to check access within the checkAccess method of the ActiveController class:
public function checkAccess($action, $model = null, $params = [])
{
// only an image owner can request the related 'delete' or 'update' actions
if ($action === 'update' or $action === 'delete') {
if ($model->user_id !== \Yii::$app->user->identity->id)
throw new \yii\web\ForbiddenHttpException('You can only '.$action.' images that you\'ve added.');
}
}
Note that the checkAccess is by default an empty method that is manually called inside all the built-in actions in ActiveController. the Idea is to pass the action ID and the model instance to it just after retrieving it from DB and before modifying it so we can do extra checks. If you just need to perform checks by actions ID then yii\filters\AccessControl may be enough but inside checkAccess you are expecting to also get the model instance itself so it is important to note that when building your own actions or overriding existing onces. be sure to manually invoke it the same way it is done in UpdateAction.php or DeleteAction.php.
whole rows are being returned but I want only few .. matching with .. current requester/user
It depends on how your data is structured. You can override ActiveController's actions to filter results before outputting them, it can be handled in the related SearchModel class if you are using one or it can be handled in model. A quick tip may be by simply overriding the find method inside your model:
public static function find()
{
return parent::find()->where(['user_id' => Yii::$app->user->getId()]); // or Yii::$app->user->identity->id
}
Note that this works only when using ActiveRecord. Which means when using this:
$images = Image::find()->all();
The find method we just overriden will be filtered by default by always including that where condition before generating the DB query. Also note the default built-in actions in ActiveController are using ActiveRecords but if you are using actions where you are constructing the SQL queries using the Query Builder then you should manually do the filtering.
The same can be done if using ActiveQuery (maybe better explained here) by doing this:
public static function find()
{
$query = new \app\models\Image(get_called_class());
return $query->andWhere(['user_id' => Yii::$app->user->getId()]);
}

Admin section in ZendFramework application

I have an application at the moment using Zend_Auth for user access. The site has an admin section where I want one user who has the role of admin in my database to be allowed access when he uses his credentials. Is Zend_Acl the only way to do this? As it seems a little complex for what I want to do or would there be any easier solutions to my problem?
I have had a think about this and I am now wondering if it is possible to have two auth controllers one for users and one for my admin section?
I did something like this recently. Create a front-controller plugin for the admin module that checks the user credential. Something like:
class Admin_Plugin_Auth extends Zend_Controller_Plugin_Abstract
{
public function preDispatch(Zend_Controller_Request_Abstract $request)
{
if ($request->getModuleName() != 'admin'){
return;
}
$auth = Zend_Auth::getInstance();
if (!$auth->hasIdentity()){
// send him to login
}
$user = $auth->getIdentity();
if (!$user->isAdmin()){ // or however you check
// send him to a fail page
}
}
}
I decided to go with the method of having a field of "is_admin" in my database if its set to 1 the user is an admin. I then use this code:
public function init()
{
$this->auth=Zend_Auth::getInstance();
if ($this->auth->getStorage()->read()->is_admin) {
$route = array('controller'=>'admin', 'action'=>'index');
} else {
$route = array('controller'=>'index', 'action'=>'index');
$this->_helper->redirector->gotoRoute($route);
}
}
This redirects the user from the admin area if they are not an admin and allows them access if they are an admin.. A lot easier to implement than ACL for the simple use in my application.

In Zend, is there a better way to check if a user hasIdentity()?

Currently, I am using Zend_Auth::getInstance()->hasIdentity() to check if my user is logged in in every controller that requires a login. I feel like I am practicing Zend horribly, so I wanted to ask the more experienced and know if this is the proper way to do this? If not, could you please tell me what is?
We use a Controller plugin (bootstrapped in application.ini config file) that handles our authentications. It checks the requested controller/action in the preDispatch() phase and matches against ACL objects (could be fetched out of DB, config files, XML, etc.). If the user does not have the privilege to access the target controller/action, the a message is stored in the session and user is redirected to another page, displaying the access forbidden message.
If the user needs to have authentication to access the target controller/action, user is redirected to the login action by modifying the request object.
Using this plugin there is no need to check for user authentication/ACL in each controller and so all the "Access" code would be enclosed in one file, the "Access Plugin".
In order to check for user identity we mostly use the same method of "Zend_Auth::getInstance()->hasIdenity()", but this just shows if the user is authenticated or not. the '''getIdentity()''' method of Zend_Auth returns the current user identity, but again just the identity and not more. However if you would need more information of the user, you could store the user information in a session.
We implement our users as data models, so each user is defined as an object. after a user is authenticated on the login action, we create the appropriate user object and store it in the user session, like this:
// This could be a sample code in AuthController/processloginAction()
// suppose $username is validated before and stores the username
$user = new Default_Model_User($username);
// now $user is our user object, suppose $log is a Zend_Log instance
$log->info("user id '{$user->getId()}' username: '{$user->getUsername()}' logged in");
$sess = Zend_Session_Namespace('auth');
$sess->user = $user;
From now one, the $user property of the session namespace of 'auth' is the user object with all the information you would need about, not just the identity. and whenever you wanted to check if user is logged in (beside using Zend_Auth) you could check for availability of this value on the user session:
$sess = Zend_Session_Namespace('auth');
if (!isset($sess->user) || !$sess->user) {
// user is not logged in, redirect to login page
}
$user = $sess->user;
/*#var $user Default_Model_User*/
$email = $user->getEmail();
now we checked for authentication, and have access to user information (email, phone, etc.).
I use a method similar to the method described by Herman Radtke in his blog at http://www.hermanradtke.com/blog/more-reliable-authentication-in-zend-framework/. Basically create a controller plugin as farzad mentioned:
class My_Authentication extends Zend_Controller_Plugin_Abstract
{
private $_whitelist;
public function __construct()
{
$this->_whitelist = array(
'index/login'
);
}
public function preDispatch(Zend_Controller_Request_Abstract $request)
{
$controller = strtolower($request->getControllerName());
$action = strtolower($request->getActionName());
$route = $controller . '/' . $action;
if (in_array($route, $this->_whitelist)) {
return;
}
$auth = Zend_Auth::getInstance();
if ($auth->hasIdentity()) {
return;
}
self::setDispatched(false);
// handle unauthorized request...
}
}
and then register that plugin in your bootstrap:
public function run() {
$front->registerPlugin(new My_Authentication());
}
I generally take this approach a little farther and integrate the Zend_Acl into the system as well. To do that I would define the plugin below:
class My_Acl_Authentication extends Zend_Controller_Plugin_Abstract
{
private $_acl;
public function __construct($acl)
{
$this->_acl = $acl
}
public function preDispatch(Zend_Controller_Request_Abstract $request)
{
$controller = strtolower($request->getControllerName());
$action = strtolower($request->getActionName());
$route = $controller . '/' . $action;
if (in_array($route, $this->_whitelist)) {
return;
}
$auth = Zend_Auth::getInstance();
$role = 'anonymous';
if ($auth->hasIdentity()) {
$role = $auth->getStorage->read()->role;
}
if ($this->_acl->isAllowed($role, $route)) {
return;
}
self::setDispatched(false);
// handle unauthorized request...
}
}
If you go this route there is some more work to be done, specifically you have to setup the ACL and then you also have to store the user's role in the auth storage.
Thats perfectly ok to do so but to save you from repeating that code you can extend all your controllers from a class A which is subclass of Zend_Controller_Action . Then inside this class declare a method
protected function hasIdentity()
{
return Zend_Auth::getInstance()->hasIdentity();
}
Now in your controller which is subclass of A you can simply do $this->hasIdentity(); instead

Can (and should?) Zend_Auth return class as the Identity?

I have a class R00_Model_User, which, curiously enough, represents user as he is. Can $result->getIdentity() return me an object of this class? (Or maybe it's stupid?)
(There is a factory method in R00_Model_User which prevents from duplicating objects. I'd like Zend_Auth to use it instead of creating a new object, if it can)
Two options:
write your own authentication adapter subclassing the out-of-the-box-adapter that matches your scenario best
class R00_Auth_Adapter extends Zend_Auth_Adapter_*
{
/**
* authenticate() - defined by Zend_Auth_Adapter_Interface. This method is called to
* attempt an authentication. Previous to this call, this adapter would have already
* been configured with all necessary information to successfully connect to a database
* table and attempt to find a record matching the provided identity.
*
* #throws Zend_Auth_Adapter_Exception if answering the authentication query is impossible
* #return Zend_Auth_Result
*/
public function authenticate()
{
$result = parent::authenticate();
if ($result->isValid() {
return new Zend_Auth_Result(
$result->getCode(),
R00_Model_User::load($result->getIdentity()),
$result->getMessages()
);
} else {
return $result;
}
}
}
This will allow you to code
$adapter = new R00_Auth_Adapter();
//... adapter initialisation (username, password, etc.)
$result = Zend_Auth::getInstance()->authenticate($adapter);
and on successfull authentication your user-object is automatically stored in the authentication storage (session by default).
or use your login-action to update the stored user identity
$adapter = new Zend_Auth_Adapter_*();
$result = $adapter->authenticate();
if ($result->isValid()) {
$user = R00_Model_User::load($result->getIdentity());
Zend_Auth::getInstance()->getStorage()->write($user);
}
In one of my applications, I have getIdentity() return a user object, and it works pretty well for me. To use your factory method, do like this:
$auth = Zend_Auth::getInstance();
$user = R00_Model_User::getInstance(...);
$auth->getStorage()->write($user);
Then when you call getIdentity(), you will have your user object.