ASP.NET MVC: Using ProfileRequiredAttribute to restrict access to pages
If you are using AppFabric Access Control Services to authenticate users when they log in to your community site using Live ID, Google or some other popular identity provider, you need more than AuthorizeAttribute to make sure that users can access the content that is there for authenticated users only. In this posting I will show you hot to extend the AuthorizeAttribute so users must also have user profile filled.
Semi-authorized users
When user is authenticated through external identity provider then not all identity providers give us user name or other information we ask users when they join with our site. What all identity providers have in common is unique ID that helps you identify the user.
Example. Users authenticated through Windows Live ID by AppFabric ACS have no name specified. Google’s identity provider is able to provide you with user name and e-mail address if user agrees to publish this information to you. They both give you unique ID of user when user is successfully authenticated in their service.
There is logical shift between ASP.NET and my site when considering user as authorized.
For ASP.NET MVC user is authorized when user has identity. For my site user is authorized when user has profile and row in my users table. Having profile means that user has unique username in my system and he or she is always identified by this username by other users.
My solution is simple: I created my own action filter attribute that makes sure if user has profile to access given method and if user has no profile then browser is redirected to join page.
Illustrating the problem
Usually we restrict access to page using AuthorizeAttribute. Code is something like this.
[Authorize]
public ActionResult Details(string id)
{
var profile = _userRepository.GetUserByUserName(id);
return View(profile);
}
If this page is only for site users and we have user profiles then all users – the ones that have profile and all the others that are just authenticated – can access the information. It is okay because all these users have successfully logged in in some service that is supported by AppFabric ACS.
In my site the users with no profile are in grey spot. They are on half way to be users because they have no username and profile on my site yet. So looking at the image above again we need something that adds profile existence condition to user-only content.
[ProfileRequired]
public ActionResult Details(string id)
{
var profile = _userRepository.GetUserByUserName(id);
return View(profile);
}
Now, this attribute will solve our problem as soon as we implement it.
ProfileRequiredAttribute: Profiles are required to be fully authorized
Here is my implementation of ProfileRequiredAttribute. It is pretty new and right now it is more like working draft but you can already play with it.
public class ProfileRequiredAttribute : AuthorizeAttribute
{
private readonly string _redirectUrl;
public ProfileRequiredAttribute()
{
_redirectUrl = ConfigurationManager.AppSettings["JoinUrl"];
if (string.IsNullOrWhiteSpace(_redirectUrl))
_redirectUrl = "~/";
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
var httpContext = filterContext.HttpContext;
var identity = httpContext.User.Identity;
if (!identity.IsAuthenticated || identity.GetProfile() == null)
if (filterContext.Result == null)
httpContext.Response.Redirect(_redirectUrl);
}
}
All methods with this attribute work as follows:
- if user is not authenticated then he or she is redirected to AppFabric ACS identity provider selection page,
- if user is authenticated but has no profile then user is by default redirected to main page of site but if you have application setting with name JoinUrl then user is redirected to this URL.
First case is handled by AuthorizeAttribute and the second one is handled by custom logic in ProfileRequiredAttribute class.
GetProfile() extension method
To get user profile using less code in places where profiles are needed I wrote GetProfile() extension method for IIdentity interface. There are some more extension methods that read out user and identity provider identifier from claims and based on this information user profile is read from database. If you take this code with copy and paste I am sure it doesn’t work for you but you get the idea.
public static User GetProfile(this IIdentity identity)
{
if (identity == null)
return null;
var context = HttpContext.Current;
if (context.Items["UserProfile"] != null)
return context.Items["UserProfile"] as User;
var provider = identity.GetIdentityProvider();
var nameId = identity.GetNameIdentifier();
var rep = ObjectFactory.GetInstance<IUserRepository>();
var profile = rep.GetUserByProviderAndNameId(provider, nameId);
context.Items["UserProfile"] = profile;
return profile;
}
To avoid round trips to database I cache user profile to current request because the chance that profile gets changed meanwhile is very minimal. The other reason is maybe more tricky – profile objects are coming from Entity Framework context and context has also HTTP request as lifecycle.
Conclusion
This posting gave you some ideas how to finish user profiles stuff when you use AppFabric ACS as external authentication provider. Although there was little shift between us and ASP.NET MVC with interpretation of “authorized” we were easily able to solve the problem by extending AuthorizeAttribute to get all our requirements fulfilled. We also write extension method for IIdentity that returns as user profile based on username and caches the profile in HTTP request scope.
Thanks you for your great posts. Helped me a lot to get around WIF and Live ID missing Name property.
Pingback:ASP.NET and WIF: Showing custom profile username as User.Identity.Name | Gunnar Peipman - Programming Blog
Pingback:ASP.NET MVC: How to implement invitation codes support | Gunnar Peipman - Programming Blog
I too have a “Registered User without a profile problem”. However, before all the MS stuff the registration and profile where just one row in a table. (less secure but that is the past).
So to get the user’s profile filled out because it’s info is used in my website, I built a SQL Server View on the database with the 2 tables, AspNetUsers, and “AccountProfile” left joined on AspNetUsers Id field. Now, user logs in and is directed to their “Account” page that has both. Or they can go to the menu and pick “Edit Profile”. In my personal need, this works dandy. when the call to the Controller to update profile with new or changed info, I just update the fields in “AccountProfile”. The SQL Server view server multiple purposed. I can just display public view of Accounts with limited information.
The SQL Server view serves multiple purposes.
Is what I was trying to type.
public see only part of SQL Server View
registered user sees also a limited display of all his compadres
registered user can see his own profile and edit it.
same SQL Sever View with different portions used.
I have read so many articles or reviews about the blogger lovers except this
article is genuinely a nice article, keep it up.
These are truly great ideas in about blogging.
You have touched some fastidious points here. Any way keep up wrinting.
With havin so much written content do you ever run into any issues
of plagorism or copyright violation? My blog has a lot of unique content I’ve either authored myself or outsourced
but it appears a lot of it is popping it up all over the internet without my authorization. Do
you know any methods to help prevent content from being stolen? I’d
really appreciate it.
Thank you for another informative site. The place else may
I am getting that kind of information written in such an ideal approach?
I have a challenge that I’m just now working on, and I’ve been on the look out for such info.
I was excited to uncover this page. I want to to thank you for your time for this
particularly fantastic read!! I definitely liked every
bit of it and i also have you saved to fav to look at new information on your website.
It’s really a nice and useful piece of info.
I am satisfied that you just shared this helpful info with
us. Please keep us up to date like this. Thank you for sharing.
Great article. You’ve made some excellent observations.
Will share this with others
Hmm it appears like your blog ate my first comment (it was super long) so I guess I’ll just sum
it up what I wrote and say, I’m thoroughly enjoying your blog.
I too am an aspiring blog writer but I’m still new to everything.
Do you have any helpful hints for rookie blog writers?
I’d genuinely appreciate it.
I’m really enjoying the design and layout of your website.
It’s a very easy on the eyes which makes it much more pleasant for
me to come here and visit more often. Did you hire out a designer to
create your theme? Great work!
If you would like to improve your experience simply keep visiting this site and be updated
with the most recent gossip posted here.
Hi would you mind letting me know which hosting company you’re using?
I’ve loaded your blog in 3 different browsers and I must say this blog loads a lot quicker
then most. Can you suggest a good web hosting provider at a
fair price? Cheers, I appreciate it!
I’m more than happy to discover this page. I want
to to thank you for your time just for this fqntastic read!!
I definitely appreciated every bit of it and i also have you bookmarked
to see new things in your web site.
Hey just wanted to give you a quick heads up. The text in your article seem to be running off the screen in Internet explorer.
I’m not sure if this is a format issue or something to do with web browser compatibility but I figured I’d post to let you
know. The style and design look great though! Hope you
get the problem fixed soon. Cheers
I am extremely impressed together with your writing abilities as smartly as with the format on your weblog.
Is that this a paid topic or did you modify it your self?
Either way stay up the excellent high quality writing, it’s uncommon to peer a nice weblog
like this one today..
Amazing blog! Is your theme custom made or did you download
it from somewhere? A design like yours with a few simple adjustements would really make my blog shine.
Please let me know where you got your design. Bless you