Custom Role Provider has issue with AuthorizeAttribute for MVC - model-view-controller

I am developing a MVC 5 application with custom role provider, but it seems that the AuthorizeAttribute never call my customer role provider, my code is as below:
My Customer provider:
namespace MyDomain
{
public class CustomRoleProvider : RoleProvider
{
public override string[] GetRolesForUser(string username)
{
using (MyContext objContext = new MyContext())
{
var objUser = objContext.Users.FirstOrDefault(x => x.Username == username);
if (objUser == null)
{
return null;
}
else
{
string[] ret = { objUser.Access_Levels.Name };
return ret;
}
}
}
public override bool IsUserInRole(string username, string roleName)
{
var userRoles = GetRolesForUser(username);
return userRoles.Contains(roleName);
}
}
My controller:
[Authorize(Roles = "Administrator")]
public class AdminController : Controller
And Web.Config:
<system.web>
<roleManager defaultProvider="CustomRoleProvider" enabled="true" >
<providers>
<clear />
<add name="CustomRoleProvider" type="Online_Storage_Portal.CustomRoleProvider" cacheTimeoutInMinutes="30"/>
</providers>
</roleManager>
</system.web>
Also my custom role provider is in the same project as my other controllers, I am able to call my custom role provider method with following code within my controller
String[] roles = Roles.GetRolesForUser(username)
but the controller with [Authorize(Roles = "Administrator")] always redirect the page to login screen even the user login and role are both valued.
Please help!!

I believe I've found the source of your problem. I'm going to assume you're using Windows Authentication, and trying to use your custom role provider in place of the Windows Groups that are automatically loaded. Looking into the MVC AuthorizeAttribute source, you'll find that it is actually calling Principal.IsInRole. Per MSDN:
InRole first checks the IsRoleListCached property to determine whether a cached list of role names for the current user is available. If the IsRoleListCached property is true, the cached list is checked for the specified role. If the IsInRole method finds the specified role in the cached list, it returns true.
If IsInRole does not find the specified role, it calls the GetRolesForUser method of the default Provider instance to determine whether the user name is associated with a role from the data source for the configured ApplicationName value.
So I'm guessing that because the Principal is a Windows Principal, it is coming in with it's roles populated and cached. When the IsInRole is called, it says 'Hey, I've already got roles, why would I go back to the provider to get them again?"
What you could do instead would be somthing like this:
protected void Application_PostAuthenticateRequest(object sender, EventArgs e)
{
WindowsIdentity identity = HttpContext.Current.Request.LogonUserIdentity;
HttpContext.Current.User = new System.Security.Principal.GenericPrincipal(new GenericIdentity(identity.Name), Roles.GetRolesForUser());
}
This will pull the windows identity off the HttpContext, use the name to explicitly fetch roles from your custom provider, and then slap a new GenericPrincipal on the request instead. I went further and implemented some logic to store the roles in an encrypted cookie so we don't have to go the role provider on each request.
void Application_PostAuthenticateRequest()
{
HttpCookie authCookie = Context.Request.Cookies[FormsAuthentication.FormsCookieName];
FormsAuthenticationTicket authTicket;
if (authCookie == null || authCookie.Value == "")
{
string[] getRoles = Roles.GetRolesForUser();
authTicket = new FormsAuthenticationTicket(1,
User.Identity.Name,
DateTime.Now,
DateTime.Now.AddMinutes(20),
true,
String.Join(";", getRoles));
string encryptedTicket = FormsAuthentication.Encrypt(authTicket);
authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
HttpContext.Current.Response.Cookies.Add(authCookie);
}
try
{
authTicket = FormsAuthentication.Decrypt(authCookie.Value);
}
catch
{
return;
}
string[] roles = authTicket.UserData.Split(';');
if (Context.User != null)
Context.User = new System.Security.Principal.GenericPrincipal(Context.User.Identity, roles);
}

Related

Role based authorization in asp.net web api using ad access token

I need to implement role based authorization for asp .net web api
from native client application the access token will be sent in the header ,
so when i give [Authorize] attribute to my web api it is working fine,but when i give [Authorize(Role="Admin")] it is giving unauthorized error,
then whenever check for role in the claims it is always null.
My startup.cs is
public void Configuration(IAppBuilder app)
{
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Audience = ConfigurationManager.AppSettings["ida:Audience"],
Tenant = ConfigurationManager.AppSettings["ida:Tenant"],
});
}
and i have applied authorize attribute for a method
[Authorize]
[HttpGet]
public async Task<UserDetails> TestAuthorization()
{
string upn = ClaimsPrincipal.Current.FindFirst(ClaimTypes.Name).Value;
string tenantID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;
var role = ClaimsPrincipal.Current.FindFirst(ClaimTypes.Role);
return null;
}
here the role value is always null
Please help me on this

Store owin oauth bearer token

I am creating a simple authentication server using the default owin oauth server. After supplying the correct credentials a bearer token is generated and returned to the client. I used among others this tutorial by Taiseer
I would like to store the token in a database before the token is send to the client.
Maybe I completely overlooked it, but where can I get the token before it is send? As far as I know the token is generated after the ticket is validated in the GrantResourceOwnerCredentials method.
I am guessing the token is stored in the context. How can I get it out?
Startup.cs
private void ConfigureAuthServer(IAppBuilder app) {
// Configure the application for OAuth based flow
var oAuthServerOptions = new OAuthAuthorizationServerOptions {
//For Dev enviroment only (on production should be AllowInsecureHttp = false)
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/oauth/token"),
Provider = new ApplicationOAuthProvider(),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14)
};
// Enable the application to use bearer tokens to authenticate users
app.UseOAuthAuthorizationServer(oAuthServerOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}
ApplicationOAuthProvider
public override Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context) {
//Dummy check here
if (context.UserName != context.Password) {
context.SetError("invalid_grant", "The user name or password is incorrect");
return Task.FromResult<object>(null);
}
var claims = new List<Claim> {
new Claim(ClaimTypes.NameIdentifier, context.UserName),
new Claim(ClaimTypes.Name, context.UserName)
};
var oAuthIdentity = new ClaimsIdentity(claims, OAuthDefaults.AuthenticationType);
AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, new AuthenticationProperties());
context.Validated(ticket);
return Task.FromResult<object>(null);
}
public override Task TokenEndpoint(OAuthTokenEndpointContext context) {
foreach (KeyValuePair<string, string> property in context.Properties.Dictionary) {
context.AdditionalResponseParameters.Add(property.Key, property.Value);
}
return Task.FromResult<object>(null);
}
Note: for those who wonder why I want to store the tokens.. it is a requirement I have to fulfill.
To fetch the token before it is sent to the client you must override TokenEndpointResponse:
public override Task TokenEndpointResponse(OAuthTokenEndpointResponseContext context)
{
return base.TokenEndpointResponse(context);
}
the context object has a property AccessToken which will contains the representation of the token as a string.
OAuthTokenEndpointResponseContext contains a dictionary of objects
IDictionary<string, object> in AdditionalResponseParameters which allows us to find all the claims for the indentity.
If we wanted to fetch the expiration of the token we would find the claim .expires in the dictionary:
context.AdditionalResponseParameters[".expires"]
There's a github repository if someone is interested to play with a simple integration of client and server interaction.

Web Api 2: Authorising against custom claims

In certain controllers, I want to authorize a user against a company ID.
For example, consider the following resource:
api/v1/companies/1234/orders
This should only be accessible by users who belong to company 1234.
Note that I am using OAuth token bearer authentication.
Creating the company claim in OAuthAuthorizationServerProvider:
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
//...
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
// Creating the companyId claim
identity.AddClaim(new Claim("CompanyId", user.CompanyId.ToString()));
context.Validated(identity);
}
The current controller implementation for the orders resource:
[RoutePrefix("api/v1/companies/{companyId:Guid}/orders")]
public class OrdersController : ApiController
{
[Route]
public IHttpActionResult GetOrders(Guid companyId)
{
var orders = OrdersRepository.Get(companyId);
return Ok(orders.Select(x => OrderModel.From(x)));
}
}
Where do I authorize the companyId URL value against the identity claim?
Can [Authorize] be somehow used here?
Here is a custom authorize filter you could use
using System.Net.Http;
using System.Web;
using System.Web.Http;
using System.Web.Http.Controllers;
public class AuthorizeAction : AuthorizeAttribute {
public string CompanyId;
protected override bool IsAuthorized(HttpActionContext actionContext) {
if (String.IsNullOrEmpty(CompanyId))
{
var routeData = actionContext.Request.GetRouteData();
var myId = routeData.Values["CompanyId"] as string;
CompanyId = myId;
}
var user = actionContext.RequestContext.Principal as ClaimsPrincipal;
if (user == null || !user.Identity.IsAuthenticated)
return false;
if (user.Claims.Any(claim => claim.Type.Equals("CompanyId") && claim.Value.Equals(CompanyId)))
return true;
return false;
}
}
You could also decorate your action or controller with below if you wanted only a single company to access a action or controller.
[AuthorizeAction(CompanyId = "1234")]
See the approach in https://github.com/AzureADSamples/NativeClient-DotNet/blob/master/TodoListService/Controllers/TodoListController.cs - instead of "scope" you can use your custom claim type

Token based authorization implementation in MVC5

let me first set the stage with the cast of characters:
An MVC 5 application
A WebAPI
I need to implement a token based security to access #2 from #1.
What I have already:
Created a startup class in #2
public void Configuration(IAppBuilder app)
{
// token generation
app.UseOAuthAuthorizationServer(new OAuthAuthorizationServerOptions
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromHours(8),
Provider = new MyServerProvider()
});
// token consumption
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
var httpConfiguration = new HttpConfiguration();
WebApiConfig.Register(new HttpConfiguration());
app.UseWebApi(httpConfiguration);
}
Standard code available everywhere on the internet.
This is the code in MyServerProvider also in #2
public class MyServerProvider: OAuthAuthorizationServerProvider
{
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated();
await Task.FromResult(0);
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
if (context.UserName == "one" && context.Password == "two")
{
var id = new ClaimsIdentity(context.Options.AuthenticationType);
id.AddClaim(new Claim("name", context.UserName));
id.AddClaim(new Claim("role", "user"));
context.Validated(id);
}
else
{
context.Rejected();
}
await Task.FromResult(0);
}
}
And another class that provides the token also in #3
public class TokenProvider
{
public TokenResponse _tokenValue { get; set; }
public string _accessToken { get; set; }
public string GetToken(string tokenEndpoint, string userName, string password)
{
var client = new OAuth2Client(new Uri(tokenEndpoint));
var tokenResponse = client.RequestResourceOwnerPasswordAsync(userName, userName).Result;
_tokenValue = tokenResponse;
_accessToken = _tokenValue.AccessToken;
return _accessToken;
}
}
So far so good.
Q1. Now when a request from a controller hits the api or the api is
called form JavaScript, what happens?
Q2. Which method from the
above get's called?
Q3. What does GrantResourceOwnerCredentials do?
Q4. What does the context object in the above question has and how
does one add the userName and Password to it and how are claims store in a cookie?
Q5. If I have to store the token in a cookie and use it for subsequent requests, do I write
that code in OnActionExecuting method of the controller in #1?
This all may sound very specific but it isn't. I am trying to understand token based authentication from a real world scenario and I am new to this.
I have gone through the samples at ThinkTecture Github repo and they all do a good job in explaining them but I am stuck at implementing it.
Any help is greatly appreciated.
Regards.

Change AD password from MVC3 application

I am building a MVC3 web application with WIF and ADFS 2.0. What I want to do is to provide a change password-function for my users, they will be changing their AD password from this web application. Since I am in the development stage, I have to be able to change AD password from my development computer (outside domain). Later, permissions will be delegated to a user that runs the service with sufficient access.
I want to do this role based, without typing in username and password and I can't seem to find any resources which points me in the right direction.
Any suggestions?
There isn't anything specific in WIF or AD FS for changing user passwords. You'll have to use the standard AD functionality provided in the System.DirectoryServices namespace.
Here's some sample code for changing a password in AD:
internal UserPrincipal GetUser(string userName)
{
PrincipalContext principalContext = new PrincipalContext(ContextType.Domain, "YourADController",
"YourADContainer",
"ADAdminUser", "ADAdminPassword");
UserPrincipal user = UserPrincipal.FindByIdentity(principalContext, userName);
return user;
}
internal void ResetPassword(string userName, string newPassword)
{
try
{
//
// Update normal AD attributes
//
UserPrincipal user = GetUser(userName);
user.SetPassword(newPassword);
}
catch (PasswordException)
{
throw new Exception("Password does not meet complexity requirements");
}
}
internal void SetPassword(string userName, string oldPassword, string newPassword)
{
try
{
//
// Update normal AD attributes
//
UserPrincipal user = GetUser(userName);
user.ChangePassword(oldPassword, newPassword);
}
catch (PasswordException)
{
throw new Exception("Password does not meet complexity requirements");
}
}

Resources