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.

Is user authorized?

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.

Gunnar Peipman

Gunnar Peipman is ASP.NET, Azure and SharePoint fan, Estonian Microsoft user group leader, blogger, conference speaker, teacher, and tech maniac. Since 2008 he is Microsoft MVP specialized on ASP.NET.

    5 thoughts on “ASP.NET MVC: Using ProfileRequiredAttribute to restrict access to pages

    • February 20, 2013 at 5:36 pm
      Permalink

      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

    • February 2, 2016 at 1:49 am
      Permalink

      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.

    • February 2, 2016 at 1:52 am
      Permalink

      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.

    Leave a Reply

    Your email address will not be published. Required fields are marked *