Symfony2 - Authentication lost after redirection - symfony

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.

Related

Is there a better way to implement role based access in ASP.NET framework?

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.

.net mvc routes with parameter proceeding action

I want to know if this is possible (it seems like it should be)
I would like to have a route such as:
/events/{id}/addcomment
where the {id} is a param to be used to identify the event to add a comment too. I'm aware that I could simple do /events/addcomment/{id} but this is not how I desire to route this action so this is what I've gotten so far by looking at other SO posts
I register a route in my global.asax file -
routes.MapRoute(
"AddComment",
"Events/{id}/AddComment",
new { controller = "Events", action = "AddComment", id = "" }
);
Then my action inside of my events controller -
[ActionName("{id}/AddComment")]
public ActionResult AddComment(int id)
{
Event _event = db.Events.Find(id);
var auth = OAContext.LoginRedirect("Must be logged in to add a comment");
if (auth != null)
return auth;
else if (_event == null)
return HttpNotFound();
else
return View(_event);
}
I've tried this with and without the ActionName annotation, not entirely sure what I'm doing wrong here.
Later I plan to have routes such as /events/{eventId}/comment/{commentId}/{action} that will allow users to edit/delete their comments from an event, but first I need to figure out exactly how to route this.
The reason I'm asking this is I have not seen any other samples of parameters proceeding actions in the url, so perhaps I'm just not able to do this and if so than that'd be good to know.
My question: is this url format possible and if so what is the proper way to code this into the routes and controller?
This is a route that should work:
routes.MapRoute(
"AddComment",
"Events/{id}/AddComment",
new { controller = "Events", action = "AddComment" }
);
You don't need to be using any ActionName attribute on your action for this to work. Just make sure that you have defined this route before your the default route. Also notice that since the {id} route parameter is not the last part of your route definition it cannot be optional anymore. You should always specify a value for it when generating an route.
For example:
#Html.ActionLink(
"click me",
"AddComment",
"Events",
new { id = "123" },
null
)

Symfony2 and RememberMe token exception

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

ASP.NET MVC - Preserve POST data after Authorize attribute login redirect

I have a blog post page with comments.
Any user (logged in or not) can see a form at the bottom of the page to post a comment.
When user enters the comment and she is not authorized - the user is redirected to a login/signup page.
After logged in, the user is redirected back to the action, but the POST data, containing the comment body, is lost.
I use the ASP.NET MVC Authorize attribute to require authorization on some actions:
[AcceptVerbs(HttpVerbs.Post), Authorize]
public ActionResult Create(int blogPostID, string commentBody) {
var comment = new Comment {
Body = commentBody,
BlogPostID = blogPostID,
UserName = User.Identity.Name
}
// persist the comment and redirect to a blog post page with recently added comment
}
How do you solve this problem?
Making user loggin before displaying the comment form is a bad idea here I think.
Thanks.
I would probably just save off the siteId and comment into the Session. Then create another overload for Create that doesn't take any parameters. It checks to see if these variables exist in the session - if so, pass it off to your original Create method.
To do that, you'd have to remove the Authorize attribute and just do the security check yourself. Something like this:
var user = HttpContext.User;
if (!user.Identity.IsAuthenticated)
{
Session["Comment"] = comment;
Session["SiteId"] = siteId;
return RedirectToAction("LogOn", "Account",
new { returnUrl = "/ControllerName/Create"} );
}
Then your overloaded Create:
public ActionResult Create()
{
var comment = (Session["Comment"] ?? "").ToString();
int siteId = 0;
if (Session["siteId"] != null)
siteId = (int)Session["siteId"];
return Create(siteId, comment);
}
Of course, this isn't really all that generic and doesn't handle more complex scenarios, but it's an idea. (hopefully the above code works, I haven't had a chance to test it). It seems like you could maybe do something like this via an action filter but I don't have any sample code for that.
You can use hidden field on your authorization form. Put your user's comment to that field (your initial POST data). After that you still can not use the data on your comment form if authorization form simply redirects to your comments form. So make your authorization form post to comments form, data in hidden field will be posted also, so you can use it.

How to get previous page route in Symfony?

I am looking for way to do this in 'right' symfony way.
There's a way to get the referer page from the $request variable. For example, if I was in myaction/mypage and click to myaction2/mypage2 by this getReferer() method I get 'http://myweb/myaction/mypage'.
If you are in an action method this can be done by
public function executeMyaction(sfWebRequest $request)
{
$previousUrl = $request->getReferer();
...
}
if you are somewhere else you can get the request by getting the conext
$previousUrl = $this->getContext()->getRequest()->getReferer();
For for sfWebRequest methods check the sfWebRequest API.
Note: this value could be inaccesible using proxy's

Resources