I'm trying to write custom authentication domain service. I think I understood all code which was written on this blog.
However I don't know how to specify which domain service application should use. I have one abstract domain service and second one is a concrete implementation of this service. If I build entire solution I get an error
'MainModule.Web.FormsAuthenticationService`1' is not a valid DomainService type. DomainService types cannot be abstract or generic.
I didn't find source code on blog which I mentioned before.
namespace MainModule.Web
{
using System;
using System.ServiceModel.DomainServices.Hosting;
using System.ServiceModel.DomainServices.Server;
// TODO: Create methods containing your application logic.
[EnableClientAccess()]
public abstract class FormsAuthenticationService<TUser> : DomainService, IAuthentication<TUser> where TUser : UserBase
{
protected abstract TUser GetCurrentUser(string name, string userData);
protected abstract TUser ValidateCredentials(string name, string password, string customData, out string userData);
protected virtual TUser GetDefaultUser()
{
return null;
}
public TUser GetUser()
{
IPrincipal currentUser = ServiceContext.User;
if ((currentUser != null) && currentUser.Identity.IsAuthenticated)
{
FormsIdentity userIdentity = currentUser.Identity as FormsIdentity;
if (userIdentity != null)
{
FormsAuthenticationTicket ticket = userIdentity.Ticket;
if (ticket != null)
{
return GetCurrentUser(currentUser.Identity.Name, ticket.UserData);
}
}
}
return GetDefaultUser();
}
public TUser Login(string userName, string password, bool isPersistent, string customData)
{
string userData;
TUser user = ValidateCredentials(userName, password, customData, out userData);
if (user != null)
{
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(/* version */ 1, userName,
DateTime.Now, DateTime.Now.AddMinutes(30),
isPersistent,
userData,
FormsAuthentication.FormsCookiePath);
string encryptedTicket = FormsAuthentication.Encrypt(ticket);
HttpCookie authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
HttpContextBase httpContext = (HttpContextBase)ServiceContext.GetService(typeof(HttpContextBase));
httpContext.Response.Cookies.Add(authCookie);
}
else
{
HttpContextBase httpContext = (HttpContextBase)ServiceContext.GetService(typeof(HttpContextBase));
httpContext.AddError(new FormsAuthenticationLogonException("Username or password is not correct."));
}
return user;
}
public TUser Logout()
{
FormsAuthentication.SignOut();
return GetDefaultUser();
}
public void UpdateUser(TUser user)
{
throw new NotImplementedException();
}
}
}
namespace MainModule.Web
{
using System.ServiceModel.DomainServices.Hosting;
// TODO: Create methods containing your application logic.
[EnableClientAccess()]
public class CustomAuthenticationService :FormsAuthenticationService<UserDTO>
{
protected override UserDTO GetCurrentUser(string name, string userData)
{
return new UserDTO {DisplayName = name, Name = name};
}
protected override UserDTO ValidateCredentials(string name, string password, string customData, out string userData)
{
userData = null;
UserDTO user = null;
if(name=="John" && password = "123")
{
userData = name;
user = new UserDTO {DisplayName = name, Email = "asdf"};
}
retrurn user;
}
}
}
This are classes I implemeted - it's the same code which is posted on blog. There is no exception so I can't paste an stackTrace. I just can't compile the solution
Make sure you are using the correct namespaces.
I noticed two small typos in the code that you pasted:
if(name=="John" && password = "123")
Should be:
if (name=="John" && password == "123")
retrurn user;
Should be:
return user;
Otherwise, it compiles without errors for me.
Create a new Web Application
Add a reference to System.ServiceModel.DomainServices.Hosting (ex. from "C:\Program Files (x86)\Microsoft SDKs\RIA Services\v1.0\Libraries\Server\System.ServiceModel.DomainServices.Hosting.dll")
Add a reference to System.ServiceModel.DomainServices.Server (ex. from "C:\Program Files (x86)\Microsoft SDKs\RIA Services\v1.0\Libraries\Server\System.ServiceModel.DomainServices.Server.dll")
Create a class called CustomAuthenticationService and insert the code below.
using System.ServiceModel.DomainServices.Hosting;
using System.Web;
using System.Web.Security;
using System;
using System.Security.Principal;
using System.ServiceModel.DomainServices.Server;
using System.ServiceModel.DomainServices.Server.ApplicationServices;
namespace WebApplication1.Services
{
public class UserDTO : UserBase
{
public string DisplayName { get; set; }
public string Email { get; set; }
}
public class FormsAuthenticationLogonException : System.Exception
{
public FormsAuthenticationLogonException(string message) : base(message) { }
}
// TODO: Create methods containing your application logic.
[EnableClientAccess()]
public abstract class FormsAuthenticationService<TUser> : DomainService, IAuthentication<TUser> where TUser : UserBase
{
protected abstract TUser GetCurrentUser(string name, string userData);
protected abstract TUser ValidateCredentials(string name, string password, string customData, out string userData);
protected virtual TUser GetDefaultUser()
{
return null;
}
public TUser GetUser()
{
IPrincipal currentUser = ServiceContext.User;
if ((currentUser != null) && currentUser.Identity.IsAuthenticated)
{
FormsIdentity userIdentity = currentUser.Identity as FormsIdentity;
if (userIdentity != null)
{
FormsAuthenticationTicket ticket = userIdentity.Ticket;
if (ticket != null)
{
return GetCurrentUser(currentUser.Identity.Name, ticket.UserData);
}
}
}
return GetDefaultUser();
}
public TUser Login(string userName, string password, bool isPersistent, string customData)
{
string userData;
TUser user = ValidateCredentials(userName, password, customData, out userData);
if (user != null)
{
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(/* version */ 1, userName,
DateTime.Now, DateTime.Now.AddMinutes(30),
isPersistent,
userData,
FormsAuthentication.FormsCookiePath);
string encryptedTicket = FormsAuthentication.Encrypt(ticket);
HttpCookie authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
HttpContextBase httpContext = (HttpContextBase)ServiceContext.GetService(typeof(HttpContextBase));
httpContext.Response.Cookies.Add(authCookie);
}
else
{
HttpContextBase httpContext = (HttpContextBase)ServiceContext.GetService(typeof(HttpContextBase));
httpContext.AddError(new FormsAuthenticationLogonException("Username or password is not correct."));
}
return user;
}
public TUser Logout()
{
FormsAuthentication.SignOut();
return GetDefaultUser();
}
public void UpdateUser(TUser user)
{
throw new NotImplementedException();
}
}
// TODO: Create methods containing your application logic.
[EnableClientAccess()]
public class CustomAuthenticationService : FormsAuthenticationService<UserDTO>
{
protected override UserDTO GetCurrentUser(string name, string userData)
{
return new UserDTO { DisplayName = name, Name = name };
}
protected override UserDTO ValidateCredentials(string name, string password, string customData, out string userData)
{
userData = null;
UserDTO user = null;
if (name == "John" && password == "123")
{
userData = name;
user = new UserDTO { DisplayName = name, Email = "asdf" };
}
return user;
}
}
}
remove attribute [EnableClientAccess()] from the FormsAuthenticationService abstract class.
it will compile without any error
Related
I am using the opened connect middleware to authenticate with a third party oidc provider and everything is up and running as I would expect. During the token exchange I exchange my auth code for an access token which is successful but I then need to store this bearer token for use later in subsequent requests. The token exchange is done as part of my startup class (by overriding the OpenIdConnectEvents during the OnAuthorizationCodeReceived method) in the asp.net core project and I need to store and access that token in my controllers.
As there is no “session” per se yet, what is the most effective (or recommended way) to store this token value from the startup class and make it accessible in my controllers?
Ive tried to use IMemoryCache but despite putting the value in the cache during this startup phase, when I try and access that cache in my controller, it is always empty.
Is there a better/preferred way of persisting values form the startup class for later use in the lifecycle?
I can see in HttpContext.Authentication.HttpAuthenticationFeature.Handler.Options I have access to all the OpenIdConnectOptions properties and settings for oidc, but nowhere can I see the actual token value that I stored after the token exchange.
I use a similar approach with Auth0 and JWT. I store some app_metadata on the claims server, retrieve, and use these values in my controllers for every request.
Startup.cs Configure
var options = new JwtBearerOptions
{
Audience = AppSettings.Auth0ClientID,
Authority = AppSettings.Auth0Domain
};
app.UseJwtBearerAuthentication(options);
app.UseClaimsTransformation(new ClaimsTransformationOptions
{
Transformer = new Auth0ClaimsTransformer()
});
AdminClaimType
public abstract class AdminClaimType : Enumeration
{
public static readonly AdminClaimType AccountId = new AccountIdType();
public static readonly AdminClaimType ClientId = new ClientIdType();
public static readonly AdminClaimType IsActive = new IsActiveType();
private AdminClaimType(int value, string displayName) : base(value, displayName)
{
}
public abstract string Auth0Key { get; }
public abstract string DefaultValue { get; }
private class AccountIdType : AdminClaimType
{
public AccountIdType() : base(1, "AccountId")
{
}
public override string Auth0Key => "accountId";
public override string DefaultValue => "0";
}
private class ClientIdType : AdminClaimType
{
public ClientIdType() : base(2, "ClientId")
{
}
public override string Auth0Key => "clientId";
public override string DefaultValue => "0";
}
private class IsActiveType : AdminClaimType
{
public IsActiveType() : base(3, "IsActive")
{
}
public override string Auth0Key => "isActive";
public override string DefaultValue => "false";
}
}
Auth0ClaimsTransformer
public class Auth0ClaimsTransformer : IClaimsTransformer
{
private string _accountId = AdminClaimType.AccountId.DefaultValue;
private string _clientId = AdminClaimType.ClientId.DefaultValue;
private string _isActive = AdminClaimType.IsActive.DefaultValue;
public Task<ClaimsPrincipal> TransformAsync(ClaimsTransformationContext context)
{
//TODO: Clean up and simplify AdminClaimTypes Transformer
foreach (var claim in context.Principal.Claims)
{
switch (claim.Type)
{
case "accountId":
_accountId = claim.Value ?? _accountId;
break;
case "clientId":
_clientId = claim.Value ?? _clientId;
break;
case "isActive":
_isActive = claim.Value ?? _isActive;
break;
}
}
((ClaimsIdentity)context.Principal.Identity)
.AddClaims(new Claim[]
{
new Claim(AdminClaimType.AccountId.DisplayName, _accountId),
new Claim(AdminClaimType.ClientId.DisplayName, _clientId),
new Claim(AdminClaimType.IsActive.DisplayName, _isActive)
});
return Task.FromResult(context.Principal);
}
BaseAdminController
//[Authorize]
[ServiceFilter(typeof(ApiExceptionFilter))]
[Route("api/admin/[controller]")]
public class BaseAdminController : Controller
{
private long _accountId;
private long _clientId;
private bool _isActive;
protected long AccountId
{
get
{
var claim = GetClaim(AdminClaimType.AccountId);
if (claim == null)
return 0;
long.TryParse(claim.Value, out _accountId);
return _accountId;
}
}
public long ClientId
{
get
{
var claim = GetClaim(AdminClaimType.ClientId);
if (claim == null)
return 0;
long.TryParse(claim.Value, out _clientId);
return _clientId;
}
}
public bool IsActive
{
get
{
var claim = GetClaim(AdminClaimType.IsActive);
if (claim == null)
return false;
bool.TryParse(claim.Value, out _isActive);
return _isActive;
}
}
public string Auth0UserId
{
get
{
var claim = User.Claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier);
return claim == null ? string.Empty : claim.Value;
}
}
private Claim GetClaim(AdminClaimType claim)
{
return User.Claims.FirstOrDefault(x => x.Type == claim.DisplayName);
}
}
Now in my controller classes that inherit from BaseAdminController I have access to:
AccountId
ClientId
IsActive
Auth0UserId
Anything else I want to add
Hope this helps.
So I figured it out. It is available on HttpContext via the AuthenticationManager:
var idToken = ((AuthenticateInfo)HttpContext.Authentication.GetAuthenticateInfoAsync("Cookies").Result).Properties.Items[".Token.id_token"];
Works a treat :)
I want to have multiples implementation of the IUserRepository each implementation will work with a database type either MongoDB or any SQL database. To do this I have ITenant interface that have a connection string and other tenant configuration. The tenant is been injected into IUserRepository either MongoDB or any SQLDB implementation. What I need to know is how properly change the injected repository to choose the database base on the tenant.
Interfaces
public interface IUserRepository
{
string Login(string username, string password);
string Logoff(Guid id);
}
public class User
{
public Guid Id { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
}
public interface ITenant
{
string CompanyName { get; }
string ConnectionString { get; }
string DataBaseName { get; }
string EncriptionKey { get; }
}
Is important to know that the tenant id is been pass to an API via header request
StartUp.cs
// set inject httpcontet to the tenant implemantion
services.AddTransient<IHttpContextAccessor, HttpContextAccessor>();
// inject tenant
services.AddTransient<ITenant, Tenant>();
// inject mongo repository but I want this to be programmatically
services.AddTransient<IUserRepository, UserMongoRepository>();
Sample Mongo Implementation
public class UserMongoRepository : IUserRepository
{
protected ITenant Tenant
public UserMongoRepository(ITenant tenant) :
base(tenant)
{
this.Tenant = tenant;
}
public string Login(string username, string password)
{
var query = new QueryBuilder<User>().Where(x => x.Username == username);
var client = new MongoClient(this.Tenant.ConnectionString);var server = client.GetServer();
var database = client.GetServer().GetDatabase(this.Tenant.DataBaseName);
var user = database.GetCollection<User>.FindAs<User>(query).AsQueryable().FirstOrDefault();
if (user == null)
throw new Exception("invalid username or password");
if (user.Password != password)
throw new Exception("invalid username or password");
return "Sample Token";
}
public string Logoff(Guid id)
{
throw new NotImplementedException();
}
}
Tenant
public class Tenant : ITenant
{
protected IHttpContextAccessor Accesor;
protected IConfiguration Configuration;
public Tenant(IHttpContextAccessor accesor, IDBConfiguration config)
{
this.Accesor = accesor;
this.Configuration = new Configuration().AddEnvironmentVariables();
if (!config.IsConfigure)
config.ConfigureDataBase();
}
private string _CompanyName;
public string CompanyName
{
get
{
if (string.IsNullOrWhiteSpace(_CompanyName))
{
_CompanyName = this.Accesor.Value.Request.Headers["Company"];
if (string.IsNullOrWhiteSpace(_CompanyName))
throw new Exception("Invalid Company");
}
return _CompanyName;
}
}
private string _ConnectionString;
public string ConnectionString
{
get
{
if (string.IsNullOrWhiteSpace(_ConnectionString))
{
_ConnectionString = this.Configuration.Get(this.CompanyName + "_" + "ConnectionString");
if (string.IsNullOrWhiteSpace(_ConnectionString))
throw new Exception("Invalid ConnectionString Setup");
}
return _ConnectionString;
}
}
private string _EncriptionKey;
public string EncriptionKey
{
get
{
if (string.IsNullOrWhiteSpace(_EncriptionKey))
{
_EncriptionKey = this.Configuration.Get(this.CompanyName + "_" + "EncriptionKey");
if (string.IsNullOrWhiteSpace(_EncriptionKey))
throw new Exception("Invalid Company Setup");
}
return _EncriptionKey;
}
}
private string _DataBaseName;
public string DataBaseName
{
get
{
if (string.IsNullOrWhiteSpace(_DataBaseName))
{
_DataBaseName = this.Configuration.Get(this.CompanyName + "_" + "DataBaseName");
if (string.IsNullOrWhiteSpace(_DataBaseName))
throw new Exception("Invalid Company Setup");
}
return _DataBaseName;
}
}
}
Controller
public class UsersController : Controller
{
protected IUserRepository DataService;
public UsersController(IUserRepository dataService)
{
this.DataService = dataService;
}
// the controller implematation
}
You should define a proxy implementation for IUserRepository and hide the actual implementations behind this proxy and at runtime decide which repository to forward the call to. For instance:
public class UserRepositoryDispatcher : IUserRepository
{
private readonly Func<bool> selector;
private readonly IUserRepository trueRepository;
private readonly IUserRepository falseRepository;
public UserRepositoryDispatcher(Func<bool> selector,
IUserRepository trueRepository, IUserRepository falseRepository) {
this.selector = selector;
this.trueRepository = trueRepository;
this.falseRepository = falseRepository;
}
public string Login(string username, string password) {
return this.CurrentRepository.Login(username, password);
}
public string Logoff(Guid id) {
return this.CurrentRepository.Logoff(id);
}
private IRepository CurrentRepository {
get { return selector() ? this.trueRepository : this.falseRepository;
}
}
Using this proxy class you can easily create a runtime predicate that decides which repository to use. For instance:
services.AddTransient<IUserRepository>(c =>
new UserRepositoryDispatcher(
() => c.GetRequiredService<ITenant>().DataBaseName.Contains("Mongo"),
trueRepository: c.GetRequiredService<UserMongoRepository>()
falseRepository: c.GetRequiredService<UserSqlRepository>()));
You can try injecting a factory rather than the actual repository. The factory will be responsible for building the correct repository based on the current user identity.
It might require a little more boiler plate code but it can achieve what you want. A little bit of inheritance might even make the controller code simpler.
I have an MVC 4 application that I'm building unit tests for. In my GameController, I have an Action, JoinGame, that requires the current userid. I get this with WebSecurity.CurrentUserId inside the controller.
When I run the unit test for JoinGame, UserId is not being populated. Obviously during a unit test there is no 'current' user. I'm trying to figure out how to mock one.
The first error I got was was System.ArgumentNullException: Value cannot be null. Parameter name; httpContext
After much searching, I found
How to mock httpcontext so that it is not null from a unit test?.
I followed the guidance in that link (created a HttpContextFactory,
which mocked httpcontext and also set the current controller context
to the mocked data). This didn't have any effect.
I then found this Mocking WebSecurity provider
I created a wrapper interface & class for websecurity, and mocked the wrapper & injected the websecuritywrapper into the gamecontroller. This solved the httpContext (though I presently don't really understand why this worked and the HttpContextFactory didn't), however the CurrentUserId returned by the websecuritywrapper is always 0. Even if I hardcode it to 1 insider the websecuritywrapper class (public int CurrentUserId{ get { return 1; }}
Obviously I'm doing something wrong, just not sure what. I've posted code for the unit test, the controller and the wrapper below.
public RedirectToRouteResult JoinGame(int gameid)
{
//_wr is the websecuritywrapper
int UserID = _wr.CurrentUserId; //WebSecurity.CurrentUserId;
// get userteam for this user and this game
UserTeam ut = _UserTeamRepository.GetUserTeam(userteamid:0, gameid: gameid, userid: UserID);
int intUTID = 0;
if (ut == null)
{
// no userteam found, create it
OperationStatus opStatus = _UserTeamRepository.CreateUserTeam(UserID, gameid);
if (opStatus.Status) intUTID = (int)opStatus.OperationID;
}
else {intUTID = ut.Id; }
if (intUTID > 0)
{
return RedirectToAction("Index", "ViewPlayers", new { id = intUTID });
}
else
{
return RedirectToAction("Index", "Game");
}
}
[Test]
public void Game_JoinGame_Returns_RedirectToAction()
{
UserProfile creator = new UserProfile();
UserProfile user = new UserProfile();
Game game = new Game();
ICollection<UserTeam> uteams = null;
UserTeam ut = new UserTeam();
ICollection<UserTeam_Player> utp = null;
List<Game> games = new List<Game>
{
new Game { Id = 1, CreatorId = 1, Name = "Game1", Creator = creator, UserTeams=uteams},
};
List<UserTeam> userteams = new List<UserTeam>
{
new UserTeam {Id=1, UserId = 1, GameId=1, User=user, Game = game, UserTeam_Players=utp}
};
Mock<IGameRepository> mockGameRepository = new Mock<IGameRepository>();
Mock<IUserTeamRepository> mockUserTeamRepository = new Mock<IUserTeamRepository>();
Mock<IWebSecurityWrapper> mockWSW = new Mock<IWebSecurityWrapper>();
mockUserTeamRepository.Setup(mr => mr.GetAllUserTeams()).Returns(userteams);
mockUserTeamRepository.Setup(mr => mr.GetUserTeam(0,1,1)).Returns(ut);
mockUserTeamRepository.Setup(mr => mr.CreateUserTeam(1, 1));
//Arrange
GameController Controller = new GameController(mockGameRepository.Object, mockUserTeamRepository.Object, mockWSW.Object);
// This didn't work
//HttpContextFactory.SetFakeAuthenticatedControllerContext(Controller);
//Act
RedirectToRouteResult result = Controller.JoinGame(1);
Assert.AreEqual("Index", result.RouteValues["action"]);
}
public class WebSecurityWrapper : IWebSecurityWrapper
{
public int CurrentUserId{ get { return WebSecurity.CurrentUserId; }}
public string CurrentUserName { get { return "admin_user"; } } // WebSecurity.CurrentUserName;
public bool HasUserId { get { return WebSecurity.HasUserId; } }
public bool Initialized { get { return WebSecurity.Initialized; } }
public bool IsAuthenticated { get { return WebSecurity.IsAuthenticated; } }
public bool ChangePassword(string userName, string currentPassword, string newPassword){return WebSecurity.ChangePassword(userName, currentPassword, newPassword);}
public bool ConfirmAccount(string accountConfirmationToken) { return WebSecurity.ConfirmAccount(accountConfirmationToken); }
public bool ConfirmAccount(string userName, string accountConfirmationToken) { return WebSecurity.ConfirmAccount(userName,accountConfirmationToken); }
public string CreateAccount(string userName, string password, bool requireConfirmationToken = false) { return WebSecurity.CreateAccount(userName, password, requireConfirmationToken = false); }
public string CreateUserAndAccount(string userName, string password, object propertyValues = null, bool requireConfirmationToken = false) { return WebSecurity.CreateUserAndAccount(userName, password, propertyValues = null, requireConfirmationToken = false); }
public string GeneratePasswordResetToken(string userName, int tokenExpirationInMinutesFromNow = 1440) { return WebSecurity.GeneratePasswordResetToken(userName, tokenExpirationInMinutesFromNow = 1440); }
public DateTime GetCreateDate(string userName) { return WebSecurity.GetCreateDate(userName); }
public DateTime GetLastPasswordFailureDate(string userName){ return WebSecurity.GetLastPasswordFailureDate(userName); }
public DateTime GetPasswordChangedDate(string userName) { return WebSecurity.GetPasswordChangedDate(userName); }
public int GetPasswordFailuresSinceLastSuccess(string userName) { return WebSecurity.GetPasswordFailuresSinceLastSuccess(userName);}
public int GetUserId(string userName){ return WebSecurity.GetUserId(userName);}
public int GetUserIdFromPasswordResetToken(string token) { return WebSecurity.GetUserIdFromPasswordResetToken(token); }
public void InitializeDatabaseConnection(string connectionStringName, string userTableName, string userIdColumn, string userNameColumn, bool autoCreateTables) { WebSecurity.InitializeDatabaseConnection(connectionStringName, userTableName, userIdColumn, userNameColumn, autoCreateTables); }
public void InitializeDatabaseConnection(string connectionString, string providerName, string userTableName, string userIdColumn, string userNameColumn, bool autoCreateTables) { WebSecurity.InitializeDatabaseConnection(connectionString, providerName, userTableName, userIdColumn, userNameColumn, autoCreateTables); }
public bool IsAccountLockedOut(string userName, int allowedPasswordAttempts, int intervalInSeconds) { return WebSecurity.IsAccountLockedOut(userName, allowedPasswordAttempts, intervalInSeconds); }
public bool IsAccountLockedOut(string userName, int allowedPasswordAttempts, TimeSpan interval) { return WebSecurity.IsAccountLockedOut(userName, allowedPasswordAttempts, interval); }
public bool IsConfirmed(string userName){ return WebSecurity.IsConfirmed(userName); }
public bool IsCurrentUser(string userName) { return WebSecurity.IsCurrentUser(userName); }
public bool Login(string userName, string password, bool persistCookie = false) { return WebSecurity.Login(userName, password, persistCookie = false); }
public void Logout() { WebSecurity.Logout(); }
public void RequireAuthenticatedUser() { WebSecurity.RequireAuthenticatedUser(); }
public void RequireRoles(params string[] roles) { WebSecurity.RequireRoles(roles); }
public void RequireUser(int userId) { WebSecurity.RequireUser(userId); }
public void RequireUser(string userName) { WebSecurity.RequireUser(userName); }
public bool ResetPassword(string passwordResetToken, string newPassword) { return WebSecurity.ResetPassword(passwordResetToken, newPassword); }
public bool UserExists(string userName) { return WebSecurity.UserExists(userName); }
}
The reason that you're getting 0 back when you hard code 1 is because of this line:
Mock<IWebSecurityWrapper> mockWSW = new Mock<IWebSecurityWrapper>();
The version of the IWebSecurityWrapper you're getting is a mock (since you injected it as such). Adding
mockSW.Setup(x=>x.CurrentUserId).Returns(1);
Should get you what you need. Since we're now telling the mock to return 1 when asked for the CurrentUserId
The reason HttpContextFactory didn't work is because the HttpContextFactory implementations I've seen deal with properties on the controller and I suspect your dependency on HttpContext was inside the WebSecurity class itself, hence why you need the wrapper.
I'm having an issue with a custom role provider in ASP.net MVC4. I implemented a very light weight RoleProvider which seems to work fine right up until I change
[Authorize]
public class BlahController:....
}
to
[Authorize(Roles="Administrator")]
public class BlahController:....
}
as soon as I make that change users are no longer authenticated and I get 401 errors. This is odd because my RoleProvider basically returns true for IsUSerInRole and a list containing "Administrator" for GetUserRoles. I had breakpoints in place on every method in my custom RoleProvider and found that none of them were being called.
Next I implemented my own authorize attribute which inherited from AuthorizeAttribute. In this I put in break points so I could see what was going on. It turned out that User.IsInRole(), which is called by the underlying attribute was returning false.
I am confident that the role provider is properly set up. I have this in my config file
<roleManager enabled="true" defaultProvider="SimplicityRoleProvider">
<providers>
<clear />
<add name="SimplicityRoleProvider" type="Simplicity.Authentication.SimplicityRoleProvider" applicationName="Simplicity" />
</providers>
</roleManager>
and checking which role provider is the current one using the method described here: Reference current RoleProvider instance? yields the correct result. However User.IsInRole persists in returning false.
I am using Azure Access Control Services but I don't see how that would be incompatible with a custom role provider.
What can I do to correct the IPrincipal User such that IsInRole returns the value from my custom RoleProvider?
RoleProvider source:
public class SimplicityRoleProvider : RoleProvider
{
private ILog log { get; set; }
public SimplicityRoleProvider()
{
log = LogManager.GetLogger("ff");
}
public override void AddUsersToRoles(string[] usernames, string[] roleNames)
{
log.Warn(usernames);
log.Warn(roleNames);
}
public override string ApplicationName
{
get
{
return "Simplicity";
}
set
{
}
}
public override void CreateRole(string roleName)
{
}
public override bool DeleteRole(string roleName, bool throwOnPopulatedRole)
{
return true;
}
public override string[] FindUsersInRole(string roleName, string usernameToMatch)
{
log.Warn(roleName);
log.Warn(usernameToMatch);
return new string[0];
}
public override string[] GetAllRoles()
{
log.Warn("all roles");
return new string[0];
}
public override string[] GetRolesForUser(string username)
{
log.Warn(username);
return new String[] { "Administrator" };
}
public override string[] GetUsersInRole(string roleName)
{
log.Warn(roleName);
return new string[0];
}
public override bool IsUserInRole(string username, string roleName)
{
log.Warn(username);
log.Warn(roleName);
return true;
}
public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames)
{
}
public override bool RoleExists(string roleName)
{
log.Warn(roleName);
return true;
}
}
It seems that System.Web.Security.Roles.GetRolesForUser(Username) does not get automatically hooked up when you have a custom AuthorizeAttribute and a custom RoleProvider.
So, in your custom AuthorizeAttribute you need to retrieve the list of roles from your data source and then compare them against the roles passed in as parameters to the AuthorizeAttribute.
I have seen in a couple blog posts comments that imply manually comparing roles is not necessary but when we override AuthorizeAttribute it seems that we are suppressing this behavior and need to provide it ourselves.
Anyway, I'll walk through what worked for me. Hopefully it will be of some assistance.
I welcome comments on whether there is a better way to accomplish this.
Note that in my case the AuthorizeAttribute is being applied to an ApiController although I'm not sure that is a relevant piece of information.
public class RequestHashAuthorizeAttribute : AuthorizeAttribute
{
bool requireSsl = true;
public bool RequireSsl
{
get { return requireSsl; }
set { requireSsl = value; }
}
bool requireAuthentication = true;
public bool RequireAuthentication
{
get { return requireAuthentication; }
set { requireAuthentication = value; }
}
public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext ActionContext)
{
if (Authenticate(ActionContext) || !RequireAuthentication)
{
return;
}
else
{
HandleUnauthorizedRequest(ActionContext);
}
}
protected override void HandleUnauthorizedRequest(HttpActionContext ActionContext)
{
var challengeMessage = new System.Net.Http.HttpResponseMessage(HttpStatusCode.Unauthorized);
challengeMessage.Headers.Add("WWW-Authenticate", "Basic");
throw new HttpResponseException(challengeMessage);
}
private bool Authenticate(System.Web.Http.Controllers.HttpActionContext ActionContext)
{
if (RequireSsl && !HttpContext.Current.Request.IsSecureConnection && !HttpContext.Current.Request.IsLocal)
{
//TODO: Return false to require SSL in production - disabled for testing before cert is purchased
//return false;
}
if (!HttpContext.Current.Request.Headers.AllKeys.Contains("Authorization")) return false;
string authHeader = HttpContext.Current.Request.Headers["Authorization"];
IPrincipal principal;
if (TryGetPrincipal(authHeader, out principal))
{
HttpContext.Current.User = principal;
return true;
}
return false;
}
private bool TryGetPrincipal(string AuthHeader, out IPrincipal Principal)
{
var creds = ParseAuthHeader(AuthHeader);
if (creds != null)
{
if (TryGetPrincipal(creds[0], creds[1], creds[2], out Principal)) return true;
}
Principal = null;
return false;
}
private string[] ParseAuthHeader(string authHeader)
{
if (authHeader == null || authHeader.Length == 0 || !authHeader.StartsWith("Basic")) return null;
string base64Credentials = authHeader.Substring(6);
string[] credentials = Encoding.ASCII.GetString(Convert.FromBase64String(base64Credentials)).Split(new char[] { ':' });
if (credentials.Length != 3 || string.IsNullOrEmpty(credentials[0]) || string.IsNullOrEmpty(credentials[1]) || string.IsNullOrEmpty(credentials[2])) return null;
return credentials;
}
private bool TryGetPrincipal(string Username, string ApiKey, string RequestHash, out IPrincipal Principal)
{
Username = Username.Trim();
ApiKey = ApiKey.Trim();
RequestHash = RequestHash.Trim();
//is valid username?
IUserRepository userRepository = new UserRepository();
UserModel user = null;
try
{
user = userRepository.GetUserByUsername(Username);
}
catch (UserNotFoundException)
{
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.Unauthorized));
}
//is valid apikey?
IApiRepository apiRepository = new ApiRepository();
ApiModel api = null;
try
{
api = apiRepository.GetApi(new Guid(ApiKey));
}
catch (ApiNotFoundException)
{
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.Unauthorized));
}
if (user != null)
{
//check if in allowed role
bool isAllowedRole = false;
string[] userRoles = System.Web.Security.Roles.GetRolesForUser(user.Username);
string[] allowedRoles = Roles.Split(','); //Roles is the inherited AuthorizeAttribute.Roles member
foreach(string userRole in userRoles)
{
foreach (string allowedRole in allowedRoles)
{
if (userRole == allowedRole)
{
isAllowedRole = true;
}
}
}
if (!isAllowedRole)
{
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.Unauthorized));
}
Principal = new GenericPrincipal(new GenericIdentity(user.Username), userRoles);
Thread.CurrentPrincipal = Principal;
return true;
}
else
{
Principal = null;
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.Unauthorized));
}
}
}
The custom authorize attribute is governing the following controller:
public class RequestKeyAuthorizeTestController : ApiController
{
[RequestKeyAuthorizeAttribute(Roles="Admin,Bob,Administrator,Clue")]
public HttpResponseMessage Get()
{
return Request.CreateResponse(HttpStatusCode.OK, "RequestKeyAuthorizeTestController");
}
}
In the custom RoleProvider, I have this method:
public override string[] GetRolesForUser(string Username)
{
IRoleRepository roleRepository = new RoleRepository();
RoleModel[] roleModels = roleRepository.GetRolesForUser(Username);
List<string> roles = new List<string>();
foreach (RoleModel roleModel in roleModels)
{
roles.Add(roleModel.Name);
}
return roles.ToArray<string>();
}
So the issue is not how you implement the role provider, but rather how you configure your application to use it. I could not find any issues in your configuration, though. Please make sure this is indeed how you configure your application. This post may help: http://brianlegg.com/post/2011/05/09/Implementing-your-own-RoleProvider-and-MembershipProvider-in-MVC-3.aspx. If you use the default MVC template to create the project, please check the AccountController. According to that post, you may need to do a few modifications to make a custom membership provider work. But that would not affect role providers.
Best Regards,
Ming Xu.
I don't like the custom authorization attribute because I have to remind people to use it. I chose to implement the my own IIdentity/IPrincipal class and wire it up on authorization.
The custom UserIdentity that calls the default RoleProvider:
public class UserIdentity : IIdentity, IPrincipal
{
private readonly IPrincipal _original;
public UserIdentity(IPrincipal original){
_original = original;
}
public string UserId
{
get
{
return _original.Identity.Name;
}
}
public string AuthenticationType
{
get
{
return _original.Identity.AuthenticationType;
}
}
public bool IsAuthenticated
{
get
{
return _original.Identity.IsAuthenticated;
}
}
public string Name
{
get
{
return _original.Identity.Name;
}
}
public IIdentity Identity
{
get
{
return this;
}
}
public bool IsInRole(string role){
return Roles.IsUserInRole(role);
}
}
and added this to global.asax.cs:
void Application_PostAuthenticateRequest(object sender, EventArgs e)
{
if(false == HttpContext.Current.User is UserIdentity){
HttpContext.Current.User = new UserIdentity(HttpContext.Current.User);
}
}
What stimms wrote in his comment: "What I'm seeing is that the IPrincipal doesn't seem to have the correct RoleProvider set" got me looking at the implementation of my custom authentication attribute which inherits from Attribute and IAuthenticationFilter.
using System.Web.Security;
....
protected override async Task<IPrincipal> AuthenticateAsync(string userName, string password, CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(userName) || string.IsNullOrWhiteSpace(password))
{
// No user with userName/password exists.
return null;
}
var membershipProvider = Membership.Providers["CustomMembershipProvider"];
if (membershipProvider != null && membershipProvider.ValidateUser(userName, password))
{
ClaimsIdentity identity = new GenericIdentity(userName, "Basic");
return new RolePrincipal("CustomRoleProvider", identity);
}
return null;
}
The key is in returning RolePrincipal, which points to your custom role provider.
Initially I returned new ClaimsPrincipal(identity), which gave me the problem described in the OP.
I have a WCF service like this:
[ServiceContract( SessionMode=SessionMode.Required)]
public interface IService
{
[OperationContract(IsInitiating = true, IsTerminating = false)]
void login(string id);
[OperationContract(IsInitiating = false, IsTerminating = false)]
string getdata();
}
public class Service : IService
{
public void login(string hashedid)
{
if (username != "someusername" || password != "somepassword")
{
// can not get data
}
else
{
// can get data
}
}
public string getdata()
{
return "these are data";
}
}
How can I write the method login and create the client application?
Thanks you.
[ServiceContract( SessionMode=SessionMode.Required)]
public interface IService
{
[OperationContract(IsInitiating = true, IsTerminating = false)]
void login(string username, string password);
[OperationContract(IsInitiating = false, IsTerminating = false)]
string getdata();
}
public class Service : IService
{
// todo: make threadsafe!
public static List<Guid> authenticated = new List<Guid>();
public void login(string username, string password)
{
if (username == "correctUsername" || password == "correctPassword")
{
// user has given correct username/pasword
Guid currentSessionId = OperationContext.Current.SessionId;
// note: can throw Exception when calling login twice or more, check if item exists first
authenticated.Add(currentSessionId);
}
}
public string getdata()
{
Guid currentSessionId = OperationContext.Current.SessionId;
if (List.Contains(currentSessionId)
{
return "these are data";
}
return String.Empty;
}
}
You can identify a session by the current Session id. After a user authenticates correctly you can add this session to the list of authenticated session.
Mind: This is just some pseudo code. The session id should removed when the session is cloced, the list I use is not threadsafe,... But I hope this helps you get into the right direction.