We are building an application using Symfony2 framework.
There is a custom authentication manager implemented which works fine, except when a user connects and select the 'Remember me' token. The user is logged in, can navigate throw the website, and after a moment, which is random in a range from about 5 minutes to about 1 hour (maybe more), the following exception occurs:
The UserProviderInterface implementation must return an instance of UserInterface, but returned "Symfony\Component\Security\Http\RememberMe\TokenBasedRememberMeServices".
The only solution to 'fix' this (and access the website again) at this point is to remove the rememberme token from the browser.
I couldn't really find any suggestions for this issue already, so I thought I might ask here in case anyone had already encountered the problem.
Thanks in advance.
Your user provider should either return an instance of \Symfony\Component\Security\Core\User\UserInterface or throw \Symfony\Component\Security\Core\Exception\UsernameNotFoundException in case it fails to find a user. Nothing else.
In my case it looks like this:
public function loadUserByUsername($loginOrEmail)
{
$qb = $this->dm
->getRepository('MyBundle:User')
->createQueryBuilder('u');
$qb->where()->orX()
->eq()->localName('u')->literal($loginOrEmail)->end()
->eq()->field('u.email')->literal($loginOrEmail);
$user = $qb->getQuery()->getOneOrNullResult();
if (!$user) {
throw new UsernameNotFoundException(sprintf('User "%s" not found.', $loginOrEmail));
}
return $user;
}
This approach will solve all the issues.
Look closely at \Symfony\Bridge\Doctrine\Security\User\EntityUserProvider
Hope it helped a bit :)
Related
I am trying to build a permission based Authorization for Asp.net Core while I am moving to core from .net framework. I built an Authorization system based on my needs(using Identity). Basically I am adding Claims to Roles and Users have roles. I built checkboxes for assigning roles to user and assigning claims to roles. Everything works well but here is my question:
While the system is working, let's say user1 has "Admin Role". user1 can access the pages based on the policies which are using his roles->claims. The problem starts here. While user1 is logged in, if I change the claims of "Admin" role, user1 won't be effected until he/she log off and login again.
Anyone has an idea to solve this problem?
The solution seems to work... but let me raise up a caution a bit...
Performance issue
Since you have to check the permission on database for every client request (which will have a real burden to the system). I know it seems like you're building classic mono app. But the server will still suffer from coming back and forth database hard.
The user doesn't know what's happening.
Imagine you display a report section that user usually access it frequently, but on some nice day... the browser blank out, or pop-up some dialog that she doesn't have permission to using this anymore. That's could cause real issue cause user only use what they need at the very moment. What'll happen if it's 10 minutes to the meeting and an assistance needed to print out some report and that pop-up ? (from my experienced lesson (XD)).
So I highly suggest that, on app deployemnt and user login, take all their role and claims from database once and cache them somewhere (like IMemoryCache, since we are targeting classic mono app), then check the claim on caches afterward.
Everytime user permission changed, update the cache, and log the user out right at that moment. If something bad happen. User would yelling at the person who setting the permission, not us as developers.
Seems like you have spend a few continuously hours to complete your own decent solution since last time.
Good work mate
Okay. I came up with a solution.
internal class PermissionAuthorizationHandler : AuthorizationHandler<PermissionRequirement>
{
UserManager<User> _userManager;
RoleManager<IdentityRole> _roleManager;
public PermissionAuthorizationHandler(UserManager<User> userManager, RoleManager<IdentityRole> roleManager)
{
_userManager = userManager;
_roleManager = roleManager;
}
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement)
{
if (context.User.Identity.Name == null)
{
return;
}
// Get all the roles the user belongs to and check if any of the roles has the permission required
// for the authorization to succeed.
var user = await _userManager.GetUserAsync(context.User);
var userRoleNames = await _userManager.GetRolesAsync(user);
var userRoles = _roleManager.Roles.Where(x => userRoleNames.Contains(x.Name));
foreach (var role in userRoles)
{
var roleClaims = await _roleManager.GetClaimsAsync(role);
var permissions = roleClaims.Where(x => x.Type == CustomClaimTypes.Permission &&
x.Value == requirement.Permission)
.Select(x => x.Value);
if (permissions.Any())
{
context.Succeed(requirement);
return;
}
}
}
}
and then we call this in startup.cs
services.AddScoped<IAuthorizationHandler, PermissionAuthorizationHandler>();
For more detailed answer please check: https://www.zehntec.com/blog/permission-based-authorization-in-asp-net-core/
Basically I've spent the last few days trying to figure out how to add simple Admin and Member roles onto a website I'm developing for a friend. (I am using ASP.NET Framework 5.2.7.0). I know that Microsoft has a nice role based access feature built in which allows you to put something like [Authorize Role=("Admin") at the top of the controller; however I have not been able to get it to work at all and most of the resources I've found are for ASP.NET Core.
I've tried modifying my web.config file to enable the role based access (and hopefully migrate the roles and such to my database). But since I've been unable to figure any of this out, I've tried going a more hacky route. (**I am not an advanced programmer, I've been doing this for about a year now and am in no way a pro). This is what I've basically come up with in my attempt to verify if a user is an admin (which also didn't work).
[Authorize]
public class AdminController : Controller
{
private LDSXpressContext db = new LDSXpressContext();
public ActionResult AdminPortal()
{
IsAdmin();
return View();
}
private ActionResult IsAdmin()
{
string name = User.Identity.Name;
//The User.Identity.Name stores the user email when logged in
var currentUserObject = db.accounts.Where(x => x.clientEmail == name);
Account currentUser = new Account();
foreach (var user in currentUserObject)
{
//I loop through the results, even though only one user should
//be stored in the var CurrentUserObject because it's the only
//way I know how to assign it to an object and get its values.
currentUser = user;
}
if (currentUser.role == 2) //the number 2 indicates admin in my db
{
return null;
}
else
{
//Even when this is hit, it just goes back and returns the
//AdminPortal view
return RedirectToAction("Index", "Home");
}
}
}
Now I'm nearly positive that is is NOT a very secure way to check if a signed in user is an admin, but I was hoping that it would at least work. My idea was when someone attempted to access the AdminPortal, the IsAdmin method would run and check if the user is an admin in the database. If they are, then it returns null and the AdminPortal view is displayed, if they are not an Admin, then they are redirected to the Index view on the home page. However, the AdminPortal page is always displayed to any user and this doesn't seem to work either. I've even stepped into the code and watched it run over the return RedirectToAction("Index", "Home"); action, but then it jumps back to the AdminPortal method and just returns the AdminPortal view. So my question is:
1) If anyone happens to have experience with Role Based access in ASP.NET Framework, I would love some tips on how to get it set up
or,
2) If all else fails and I need to use my hacky method, why does it continue to return the AdminView even when the user is not an admin.
**Note: I know I could create a function that returns true or false if the user is an Admin or not, and then have an if/else statement in the AdminPortal controller that will return on view for true and another for false, however I don't want to have to implement that onto every ActionMethod, it'd be nice to keep it down to one line, or just the [Authorize Role="Admin] above the controller if possible.
Thank you guys so much for any help provided, I've been trying to research and fix this for days now and decided to reach out and ask the community!
At a minimum, you'll want to make some adjustments to what you're doing:
[Authorize]
public class AdminController : Controller
{
public ActionResult AdminPortal()
{
if(IsAdmin())
{
return View();
}
return RedirectToAction("Index", "Home");
}
private bool IsAdmin()
{
bool isAdmin = false;
using(LDSXpressContext db = new LDSXpressContext())
{
string name = User.Identity.Name;
//The User.Identity.Name stores the user email when logged in
// #see https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.singleordefault
var currentUser = db.accounts.SingleOrDefault(x => x.clientEmail.Equals(name, StringComparison.OrdinalIgnoreCase));
// If the email doesn't match a user, currentUser will be null
if (currentUser != null)
{
//the number 2 indicates admin in my db
isAdmin = currentUser.role == 2;
}
}
return isAdmin;
}
}
First off, DbContext instances are meant to used, at most, per the lifetime of an HTTP request. Moving it from the class / controller level and placing it within a using block makes sure that it's properly disposed.
Next, your IsAdmin function really just needs to return a true/false value based on your lookup, and then the AdminPortal action can decide what to do with that result.
Since email seems to be a unique field in your table, use the SingleOrDefault or FirstOrDefault LINQ extension to fetch a single matching record. Which one you use is up to you, but if it's truly a unique value, SingleOrDefault makes more sense (it will throw an exception if more than one row matches). Using the StringComparison flag with the String.Equals extension method makes your search case-insensitive. There are a few culture-specific versions of that, but ordinal matching is what I would normally use, here.
Implementing some version of the Identity framework is a bit too long for an answer here, but it's possible to implement a claims-based authentication scheme without too much work. That's something that probably needs a separate answer, though.
I've been experiencing an issue with my SF2 application today.
I want the user to be automatically authenticated after submiting a valid subscription form.
So basically in my controller here's what I do:
if ($form->isValid()) {
$customer = $form->getData();
try {
$customer = $this->get('my.service.manager.customer')->customerSubscribe($customer);
} catch (APIClientException $e) {
$error = $e->getErrors();
...
}
if ($customer && !isset($error)) {
// connect customer
$token = new UsernamePasswordToken($customer, null, 'api_auth', array('ROLE_USER'));
$this->get('security.context')->setToken($token);
...
}
return new RedirectResponse($this->generateUrl('MyBundle_index'));
}
The two lines below the 'connect customer' comment actually seem to authenticate the user fine.
The problem being when I redirect to another page with RedirectResponse, then the authentication is lost.
I've tried a call to
$this->container->get('security.context')->isGranted('ROLE_USER')
which returns true just before the call to RedirectResponse, and false in my other controller where the response is being redirected.
At this point I'm a bit confused about what I'm doing wrong. Any ideas appreciated.
Btw, I'm using Symfony2.1
I've noticed this happens when you redirect more than once at a time. Does the controller for the MyBundle_index route return another redirect? If so, I think that's your answer.
Otherwise, maybe try using forwards? Instead of:
return new RedirectResponse($this->generateUrl('MyBundle_index'));
...just forward to whatever controller/action is defined for that route:
return $this->forward("SomeBundle:Default:index");
The URL that the user ends up with in their address bar might not be what you're expecting (it won't change from the one they requested originally), but you can probably fiddle with that to get it to your liking.
Ok I solved it like so:
$token = new UsernamePasswordToken($customer->getEmail(), null, 'api_auth', array('ROLE_USER'));
Apparently I needed to pass the customer id (in that case the email) as the first argument of UsernamePasswordToken, instead of the entire customer object. I'm not sure why since my entity Customer has a _toString method implemented, but at least it works fine like that.
Hope somebody can help.
Have looked around on the net but cannot seem to solve (or understand) this.
I have tried the code posted at
http://blogs.msdn.com/b/kylemc/archive/2010/05/10/using-asp-net-membership-in-silverlight.aspx
(not going to repeat the class MembershipServiceUser here as it is quite long and can be seen on the mentioned page)
I have set up the domain service with the class and the code to return the users:
//[RequiresRole("Managers")]
public IEnumerable<MembershipServiceUser> GetAllUsers()
{
return Membership.GetAllUsers().Cast<MembershipUser>().Select(u => new MembershipServiceUser(u));
}
I took out the RequiresRole for testing.
What I seem to be a bit blonde about is the calling of the GetAllUsers() method.
In my code behind I am using:
MembershipDataContext context = new MembershipDataContext();
EntityQuery<MembershipServiceUser> users = context.GetAllUsersQuery();
I am not 100% sure if this is the correct way to use the method or if something else is wrong because
context.GetAllUsersQuery(); returns "Enumeration yielded no results"
One question is also in the code kylmc uses //RequiresRole("Admin")]. Is this a custom role created in the ASP.NET Configuration editor?
Looking at another tutorial regarding using the ASP.NET authentication service in Silverlight, I create a role called "Managers" and added the login user to that role.
Logging in using a user with role Managers doesn't help and results are still not yielded.
Any ideas I could possible look at?
Many thanks
Neill
There are two steps involved with querying.
Get a query object from the Domain Service context (synchronous).
Load the query from the Domain Service context (asynchronous).
Example:
public void Load()
{
// define the query
var query = context.GetAllUsersQuery();
// start running the query, and when the results return call
// OnGetAllUsersLoaded
context.Load(query, OnGetAllUsersLoaded, null);
}
public void OnGetAllUsersLoaded(LoadOperation op)
{
var results = op.Entities;
}
I've been receiving some error reports recently that seem to suggest that sometimes User.Identity.Name is NULL or different to what it should be (the authenticated user's username), even when the page is authenticated:
[Authorize]
[HttpGet]
public ActionResult MyAction()
{
string username = User.Identity.Name;
Member member = memberRepository.GetMemberByUserName(username);
member.something // fails with a null reference exception
}
The GetMemberByUserName() method uses Linq to SQL to retrieve the member.
public Member GetMemberByUsername(string username)
{
if (String.IsNullOrEmpty(username))
return null;
return db.Members.SingleOrDefault(d => d.username.Equals(username));
}
Under which circumstances would this happen? Do I need to check if User.Identity.Name is NULL whenever I use it?
Of course, the User.Identity.Name could be a red herring, and it could be that the database is just not returning anything. I would probably have expected an exception to be thrown if this was the case, though.
EDIT:
This is an intermittent issue - it could be that an authenticated user refreshes once, the User.Identity.Name is correct and the member is retrieved, and then on a second refresh, the member is not retrieved.
it could be that the database is just not returning anything. I would probably have expected an exception to be thrown if this was the case, though.
No, it won't. SingleOrDefault will return a single record, no record, or throw an exception if more than one record exists.
Single will return a single record, or throw an exception if no record or more than one exists.
Could be a few things. Maybe you have case sensitivity issues with the String.Equals.
It's still possible that Request.IsAuthenticated (what [Authorize] looks at) is true, but there is no identity. Particulary true in custom authentication systems (such as attempting to decrypt the forms authentication ticket), and you haven't specified what your using.
Also, make sure the Username field is backed by a unique index, to provide server-side guarantees that SingleOrDefault will NEVER throw an exception.
I would put some try/catch in your controller actions, and setup ELMAH to catch the exceptions.
How about:
public Member GetMemberByUsername(string username)
{
if (String.IsNullOrEmpty(username))
return null;
return db.Members.SingleOrDefault(d => d.username.Equals(username));
}