Custom Role Authentication ASP .NET 4.5 - c#

I am really struggling with creating my own user access roles and utilising the IIdentity and IPrincipal classes.
EDIT
I have been looking at this but I can't get it to work.
I have created a simple EndUser table with email, password and roles columns. However at the moment, I don't think the roles isn't being parsed through to the IIdentity or IPrincipal and I am unsure of how to do it.
I have looked at several guides and most either rely on VS and the .NET Framework building the basis for you with many clunky looking tables, or they are MVC, which isn't helpful at all.
In my table I have three defined roles which are Client, Sales and Admin, I want certain pages inaccessible to certain users. These are then reflected in my webconfig as Denying access globally but allowing access to certain roles and only if authenticated.
At present the login functionality is working, and users are denied unless logged in but the specific roles are not being taken into account. I have an object stored in session of all of the user data across the site, but "roles" is completely ignored in the web config.
Ultimately I need to do something with uRole, but I am not sure what or how.
Could someone point me in the right direction please?
Many thanks
Login C#:
public partial class Login : Page
{
protected void Page_Load(object sender, EventArgs e)
{
}
protected void logindd_Click(object sender, EventArgs e)
{
EndUser newEndUser = new EndUser();
string user = ((TextBox)loginForm1.FindControl("UserName")).Text;
string conRef = ((TextBox)loginForm1.FindControl("Password")).Text;
using (SqlConnection con = new SqlConnection(ConfigurationManager.ConnectionStrings["LocalConnection"].ConnectionString))
{
try
{
con.Open();
string checkIdent = #"SELECT Count(*) FROM EndUser WHERE EndUser.Email=#email AND EndUser.Password=#pass";
using (SqlCommand cmd = new SqlCommand(checkIdent, con))
{
cmd.Parameters.AddWithValue("#email", user);
cmd.Parameters.AddWithValue("#pass", pass);
int chk = (int)cmd.ExecuteScalar();
if (chk > 0)
{
//grabbing user role
SqlCommand cmd2 = new SqlCommand("SELECT EndUser.Role FROM EndUser WHERE EndUser.Password=#pass;",con);
cmd2.Parameters.AddWithValue("#pass", pass);
SqlDataReader sdr = null;
sdr = cmd2.ExecuteReader();
sdr.Read();
string uRole = sdr["Role"].ToString();
UserIdentity userr = new UserIdentity(user, true, uRole);
userr.Roles.Add(uRole);
FormsAuthentication.Initialize();
FormsAuthenticationTicket fat = new FormsAuthenticationTicket(1, user, DateTime.Now, DateTime.Now.AddMinutes(30), false, userr.Roles.ToString());
Response.Cookies.Add(new HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(fat)));
newEndUser.email = user;
newEndUser.pass = pass;
Session["EndUserObj"] = newEndUser;
FormsAuthentication.RedirectFromLoginPage(user, false);
}
else
{
ErrorLbl.Visible = true;
ErrorLbl.Text = "Details are incorrect";
}
}
}
catch (Exception er)
{
ErrorLbl.Visible = true;
ErrorLbl.Text = er.ToString();
}
}
}
}

I had this issue when I was trying to sort this out a few months back. You're currently logging in at the moment, from the code that you've shown I would have to assume that this is a web forms project with pre-built Web Form kit. Therefore you're still using OWIN to login.
Not a code-based walk-through but this is what you need to do, I'm happy to assist further if you get stuck:
Change the global.asax so that it authenticates the user on Application_AthenticateRequest and remove anything related to OWIN in it's methods.
You need a class to derive from IIdentity and accept params of username, authtype and isAuthenticated and a class to derive from IPrincipal which will contain the derived IIdentity, and the role. I went for public getter and setters and private variables of things like name, is auth etc.
A class to get the identity of the user, this where you'll get against the password and username and create an instance of the derived IIdentity to set, also check against the information held within the database.
The global method Application_AthenticateRequest will need to verify the user on each page load, so you need to have a method that creates an instance of the derived IPrincipal to check whether the user is authenticated by grabbing the derived IIdentity and checking what role the user is in.
Modify the webconfig so that it uses FormsAuthentication, doesn't use OWIN, so within AppSettings add <add key="owin:AutomaticAppStartup" value="false"/> as I assume you have a pre-built site, don't try and remove OWIN, just disable it.
You should then be able to login and the custom roles you declare will then be used by the derived IIdentity which in turn will be acknowledged by the webconfig location based security.
Hope this helps you and others.

Related

SignalR .Net-Framework how to implement Authentication

since I am rather new to SignalR and couldn't really find anything online, I want to ask my questions here.
I use SignalR with C# and .Net-Framework and want to Implement a function in which i can login with Username and Password to the Host/Hubs with specific Roles, but I couldn't really find anything helpfull in this regard at the Microsoft Docs.
(https://learn.microsoft.com/en-us/aspnet/signalr/overview/security/hub-authorization)
So my Question is:
How can I implement Authentication on my SignalR-Host and is it possible to reject a connection if a the Username and Password which is send to the Host isn't correct?
Thanks for the Help
DerDane
I use an Attribute for that:
public class AuthorizeRolesAttribute : AuthorizeAttribute
{
public AuthorizeRolesAttribute(params string[] roles)
{
this.Roles = string.Join(",", roles);
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext == null)
{
throw new ArgumentNullException(nameof(httpContext));
}
// Make sure the user is authenticated.
var roles = this.Roles.Split(new [] { ',' }, StringSplitOptions.RemoveEmptyEntries);
foreach (var role in roles)
{
if (httpContext.User.IsInRole(role))
{
return true;
}
}
return false;
}
}
Usage:
[AuthorizeRoles("Admin")]
public class ExampleHub...
UPDATE
Authentication is not a trivial topic. If you want to dive deeper into it, take a look at https://learn.microsoft.com/en-us/aspnet/core/security/authentication/?view=aspnetcore-6.0 to understand how to authenticate a user. Then, at https://learn.microsoft.com/en-us/aspnet/core/security/authorization/roles?view=aspnetcore-6.0 you can see how roles are associated with a previously authenticated identity.
Generally, you will never do this manually. You use existing frameworks, which are already proven.
In httpContext.User.Identity you have access to your identity, the logged in user.
I think it can be very interesting for you to understand the pipeline of ASP .NET. Search for "asp net pipeline diagram" and also check this
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-6.0 for more information about the middleware.
There is a lot going on behind the scenes. You can just use them but it's always interesting to know them to a greater or lesser extent.
You define the roles. If your application is to manage a restaurant, you can create roles such as "waiter", "cook"... while in an educational center they can be "teacher", "student", "director", etc.
You can get a list of roles like this:
var roleStore = new RoleStore<IdentityRole>(context);
var roleMngr = new RoleManager<IdentityRole>(roleStore);
var roles = roleMngr.Roles.ToList();
To add a role to user, you can use UserManager.AddToRole.

ASP.NET Identity 2 and Anonymous Users

In our developing e-commerce solution we are using AspNet Identity 2.2.1 and it is required that any guest (anonymous) users should complete checkout without prior registration to the website. In order to fullfill this requirement have written an ActionFilter named UserMigrationAttribute which obtains SessionTrackId (string GUID) from cookie -which we set from a HttpModule for every request if SessionTrackId is not found along with request cookies- and creates and actual IdentityUser in database with the username something like SessionTrackId#mydomain.com.
We have decorated our BaseController class with this UserMigration attribute in order to utilize its functions throughout the site.
Everything up to this point works as expected with single downside issue, which is when the page is being loaded for the first time for any user, if we try to make an Jquery Ajax Call to a Method which have [ValidateAntiForgeryToken] attribute, the call fails with the 'The provided anti-forgery token was meant for a different claims-based user than the current user.' error, even though we are sending __RequestVerificationToken parameter with every ajax call.
But if user opens another page by clicking link and/or reloads/refreshes current page, all the subsequent ajax calls complete successfully.
In our understanding UserMigrationAttribute creates user on OnActionExecuting method, but after we signIn user in the process #Html.AntiForgeryToken() is not being updated with the right values.
You may find the UserMigrationAttribute code below;
[AttributeUsage(AttributeTargets.Class)]
public class UserMigrationAttribute : ActionFilterAttribute
{
public ApplicationSignInManager SignInManager(ActionExecutingContext filterContext)
{
return filterContext.HttpContext.GetOwinContext().Get<ApplicationSignInManager>();
}
public UserManager UserManager(ActionExecutingContext filterContext)
{
return filterContext.HttpContext.GetOwinContext().GetUserManager<UserManager>();
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
CreateMigrateCurrentUser(filterContext);
base.OnActionExecuting(filterContext);
}
private static readonly object LockThis = new object();
private void CreateMigrateCurrentUser(ActionExecutingContext filterContext)
{
lock (LockThis)
{
var signInManager = SignInManager(filterContext);
var userManager = UserManager(filterContext);
var sessionTrackId = GetSessionTrackId(filterContext);
if (!filterContext.HttpContext.Request.IsAuthenticated)
{
if (!string.IsNullOrEmpty(sessionTrackId))
{
var username = string.Format("{0}#mydomain.com", sessionTrackId);
var user = userManager.FindByName(username);
if (user == null)
{
user = new User() {UserName = username, Email = username};
var result = userManager.Create(user);
userManager.AddToRole(user.Id, StringResources.AnonymousVisitorsGroup);
}
signInManager.SignIn(user, true, true);
}
}
else
{
if (!string.IsNullOrEmpty(sessionTrackId))
{
var username = string.Format("{0}#mydomain.com", sessionTrackId);
var user = userManager.FindByName(username);
if (user != null)
{
if (!HttpContext.Current.User.IsInRole(StringResources.AnonymousVisitorsGroup))
{
var targetUserId = HttpContext.Current.User.Identity.GetUserId<int>();
var service = new Service();
service.Users.MigrateUser(user.Id, targetUserId);
}
}
}
}
}
}
private string GetSessionTrackId(ActionExecutingContext filterContext)
{
var retVal = string.Empty;
if (filterContext.HttpContext.Request.Cookies["stid"] != null)
{
retVal = filterContext.HttpContext.Request.Cookies["stid"].Value;
}
return retVal;
}
}
Any help or suggestions are highly appreciated.
Thank you,
This is happening because the anti-forgery token is set in a cookie, which will not be updated until the next request. If you're manually signing a user in, you should also issue a redirect (even if to the same page they were already headed to), simply to ensure that the cookie data is correct. This normally happens naturally, as the sign in form will redirect to the URL that needed authorization after the user is signed in, thus negating the problem. Since you're not redirecting currently, the data is out of sync.
However, I have to say that this seems like a very poor solution to this particular use case. Creating some sort of temporary-type user and signing that user in to handle guest checkout creates an unnecessary glut of useless data in your database, at best, and leads to bugs and other issues like this one you're experiencing, at worst.
I also run an ecommerce site, and the way we handled guest checkout is incredibly simplistic. The checkout data is just stored in the session (email, shipping/billing address, etc.). We build a view model to handle the actual checkout where the data necessary for submitting the sale comes either from the user object, if they're logged in, or these session variables, if they aren't. If the user is neither logged in, nor has the requisite session variables set, then they are redirected to the onboarding form where billing/shipping, etc. is collected.
For other aspects like maintaining an anonymous cart, we use a permanent cookie with the cart identifier. If the user ends up creating an account, we associate the anonymous cart with their user, and then remove the cookie. This ensures that their cart survives past the session timeout and things like closing the browser, even if they're anonymous.
In other words, in all these things, no user object is actually needed. If it's there (user is logged in), great, we'll use it. Otherwise, we collect and persist the requisite information for checkout via other means.

Running all SSRS reports through one report user ignoring own users domain

I have the following code which returns the reports from my SSRS server, afterwards I then store the paths to each individual list which allows users to run them from within the application. The below works fine.
NetworkCredential serviceCredentials = new NetworkCredential()
{
UserName = username,
SecurePassword = EncryptionManager.DecryptToSecureString(password),
Domain = domain
};
reports = new ObservableCollection<object>(reportsManager.FindReports(reportsWebService, reportsFolderName, serviceCredentials));
//FindReports
ReportingService2005 rs = new ReportingService2005();
rs.Url = reportsWebService;
rs.Credentials = serviceCredentials;
CatalogItem[] catalogItems = rs.ListChildren(#"/" + reportsFolderName, false);
However the problem is when a user selects a report to view it shows the following error:
The permissions granted to user are insufficient for performing this
operation.
I understand that the quick fix to this would be to add the users domain into the security section on the Report server, however this is not appropriate.
My question is I can supply credentials to allows a specified user to access the report folder is it possible to pass this along so that user can run a report?
Each of my reports use built in connection strings NOT windows authentication.
Edit: I am using Reporting WinForms.
Windows Forms
In a windows forms project you can pass a suitable System.Net.NetworkCredential to ServerReport.ReportServerCredentials.NetworkCredentials property of ReportViewer. This way, all reports will be executed using the passed credential:
reportViewer1.ServerReport.ReportServerCredentials.NetworkCredentials =
new System.Net.NetworkCredential("username", "password", "domain");
Web Forms
The solution for a Web Forms is different. In a Web Forms project, to pass a suitable credential to RePortViewer you need to implement IReportServerCredentials. Then you can assign the value to ServerReport.ReportServerCredentials property of ReportViewer control. This way, all reports will be executed using the passed credential.
Example
Here is a simple implementation. It's better to store username, password and domain name in app config and read them from config file:
using System;
using System.Net;
using System.Security.Principal;
using Microsoft.Reporting.WebForms;
[Serializable]
public sealed class MyReportServerCredentials : IReportServerCredentials
{
public WindowsIdentity ImpersonationUser { get { return null; } }
public ICredentials NetworkCredentials
{
get
{
return new NetworkCredential("username", "password", "domain");
}
}
public bool GetFormsCredentials(out Cookie authCookie, out string userName,
out string password, out string authority)
{
authCookie = null;
userName = password = authority = null;
return false;
}
}
Then in Page_Load pass the credential this way:
protected void Page_Load(object sender, EventArgs e)
{
if(!IsPostBack)
this.ReportViewer1.ServerReport.ReportServerCredentials =
new Sample.MyReportServerCredentials();
}
Note
In cases which you want to use ReportViewer with no session state you can also implement IReportServerConnection. In this case you need to add a key value in appsettings section of config file to introduce the implementation this way:
<add key="ReportViewerServerConnection" value="YourNameSpace.YourClass, YourAssemply" />
In this case, you don't need code in Page_Load and the config would be enough. For more information take a look at this great blog post by Brian Hartman.

how to allow login in with email using the asp signup component

as the title says how do o allow users to sign in using the email they specified when they registered.
using asp 3.5 , both the login and signup are the ones that are built-in visual studio.
also is there a way to remove the secret question and answer.
thanks
My simplest and effective workaround for this was to do the following:
1- Rename the label of username field as E-mail: ( so the user will see it Email, but it's actually still the username that ASP.NET membership will save).
2- Save my entry normally along side with any additional info I need to save
3- now my users will be forced to login using their provided Email
4- Smile :)
I think you are looking for forms authentication. There are alot of instructions on google. Or see here: http://msdn.microsoft.com/en-us/library/system.web.security.formsauthentication.aspx
You need to wire up the control's login click event to your authentication logic,
protected void Login_Click(object sender, EventArgs e)
{
// your logic here
}
Also something like the following should go into Global.asax.cs to create an authentication ticket (cookie) that represents the users authenticated session.
protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
String cookieName = FormsAuthentication.FormsCookieName;
HttpCookie authCookie = Context.Request.Cookies[cookieName];
if (null == authCookie)
{//There is no authentication cookie.
return;
}
FormsAuthenticationTicket authTicket = null;
try
{
authTicket = FormsAuthentication.Decrypt(authCookie.Value);
}
catch (Exception ex)
{
//Write the exception to the Event Log.
return;
}
if (null == authTicket)
{//Cookie failed to decrypt.
return;
}
//When the ticket was created, the UserData property was assigned a
//pipe-delimited string of group names.
String[] groups = authTicket.UserData.Split(new char[] { '|' });
//Create an Identity.
GenericIdentity id = new GenericIdentity(authTicket.Name, "LdapAuthentication");
//This principal flows throughout the request.
GenericPrincipal principal = new GenericPrincipal(id, groups);
Context.User = principal;
}
You will also need an 'IsAuthenticted' boolean method to contain the logic of authentication. In your case you need store their e-mail addresses somewhere and point your authentication logic at that source.

How to pass WCF UserName clientCredentialType to other service?

I have several WCF services hosted in IIS6 (should not affect this issue) on the same host, and I want, for Performance/ Maintanance and other reasons to combine several requests into 1 request using a Facade Service,
All done with special Service Contract / Service that has an operation that calls other services for several operations.
I'm using WSHTTP (probably BasicHttp in the near future) with Message security and UserName client credential type.
I want the Facade Service to use the credentials from the client. Meaning the call to the back-end service will get the credentials as if the client would call it directly.
For example:
Client calls FacadeService.CompositeOperation with UserName "A" and password "B".
Now the FacadeService.CompositeOperation needs to call BackEndService.BackendOperation setting the Credentials.UserName.UserName to "A" and Credentials.UserName.Password to "B" just like what the client done when calling to this operation. I have no way to extract this information in WCF (and it should be, because it is sensitive information) but i neither found a way to take "a token" of these and pass it forward to the backend service (I have no need to know this information in the FacadeService, only to pass them over).
In FacadeService, as in BackEndService, the authentication is made through ASP.NET provider, the authorization is a custom Role-based authorization taking the UserName from the PrimaryIdentity, so the PrimaryIdentity on the BackEndService should be set to what the client send.
How should i do it?
I read your post yesterday but wasn't sure of an answer, but seeing as you've had no replies i thought i'd add something and maybe provide some food for thought.
Firstly, would making the additonal service calls be overly intensive on resources? If not, there is an argument for code clarity, to seperate them out so in the future developers will know exactly what's happening rather than 1 service call performing multiple operations.
Are you not able to make calls to other services from your server side code from within the method you're hitting? As once, you're server side, the security context should hold the identity of the user that you're after so calls to other services would use the same identity.
Finally, I was wondering whether WCF Impersonation (MSDN LINK) might be something you can use on the server to achieve what you're after. I've not used it myself so can't advise as much as i'd like.
Hope that's of some help - good luck!
Once i tried to Store Password along with UserName in PrimaryIdentity.
To achieve this What we need to do is to provide a New UserNameSecurityTokenAuthenticator Which will authenticate UserName and Password and then can store in the Identity and then it will Store the Identity in SecurityContext of WCF.
Steps to Do
Classes
1.) TestServiceHost : ServiceHost
2.) UserNamePasswordSecurityTokenManager : ServiceCredentialsSecurityTokenManager
3.) TestUserNameSecurityTokenAuthenticator : UserNameSecurityTokenAuthenticator
4.) MyIdentity : IIdentity
5.) MyAuthorizatoinPolicy : IAuthorizationPolicy
1.) Create New ServiceHost class TestServiceHost
2.) In TestServiceHost Override OnOpening and provide a new Class UserNamePasswordServiceCredentials
protected override void OnOpening()
{
base.OnOpening();
this.Description.Behaviors.Add(new UserNamePasswordServiceCredentials());
}
3.) Then in UserNamePasswordServiceCredentials, provide new UserNamePasswordSecurityTokenManager
public override SecurityTokenManager CreateSecurityTokenManager()
{
return new UserNamePasswordSecurityTokenManager(this);
}
4.) Then in UserNamePasswordSecurityTokenManager provide new TestUserNameSecurityTokenAuthenticator
public override SecurityTokenAuthenticator CreateSecurityTokenAuthenticator(SecurityTokenRequirement tokenRequirement, out SecurityTokenResolver outOfBandTokenResolver)
{
if (tokenRequirement.TokenType == SecurityTokenTypes.UserName)
{
outOfBandTokenResolver = null;
return new TestUserNameSecurityTokenAuthenticator();
}
return base.CreateSecurityTokenAuthenticator(tokenRequirement, out outOfBandTokenResolver);
}
5.) Then Inside TestUserNameSecurityTokenAuthenticator you can Authenticate UseraName and Password and can create your own Identity. In this function you will return a list of IAuthorization policies to be evaluated. I created my own authorization Policy and passed my new identity to it, so as to set the Identity in context.
protected override System.Collections.ObjectModel.ReadOnlyCollection<System.IdentityModel.Policy.IAuthorizationPolicy> ValidateUserNamePasswordCore(string userName, string password)
{
ClaimSet claimSet = new DefaultClaimSet(ClaimSet.System, new Claim(ClaimTypes.Name, userName, Rights.PossessProperty));
List<IIdentity> identities = new List<IIdentity>(1);
identities.Add(new MyIdentity(userName,password));
List<IAuthorizationPolicy> policies = new List<IAuthorizationPolicy>(1);
policies.Add(new MyAuthorizationPolicy(ClaimSet.System, identities));
return policies.AsReadOnly();
}
public class MyAuthorizationPolicy : IAuthorizationPolicy
{
String id = Guid.NewGuid().ToString();
ClaimSet issuer;
private IList<IIdentity> identities;
#region IAuthorizationPolicy Members
public MyAuthorizationPolicy(ClaimSet issuer, IList<IIdentity> identities)
{
if (issuer == null)
throw new ArgumentNullException("issuer");
this.issuer = issuer;
this.identities = identities;
}
public bool Evaluate(EvaluationContext evaluationContext, ref object state)
{
if (this.identities != null)
{
object value;
IList<IIdentity> contextIdentities;
if (!evaluationContext.Properties.TryGetValue("Identities", out value))
{
contextIdentities = new List<IIdentity>(this.identities.Count);
evaluationContext.Properties.Add("Identities", contextIdentities);
}
else
{
contextIdentities = value as IList<IIdentity>;
}
foreach (IIdentity identity in this.identities)
{
contextIdentities.Add(identity);
}
}
return true;
}
public ClaimSet Issuer
{
get { return this.issuer; }
}
#endregion
#region IAuthorizationComponent Members
public string Id
{
get { return this.id; }
}
#endregion
}
So this example shows how you can override Security in WCF:
Now in your problem:
1.) Implement this Technique and Set UserName and Password in your identity. Now when ever you have call child service, get Identity extract Username and password from it and pass on to child service.
2.) Authenticate UserName and Password and generate a token for that (should create a new token service for that). Save this Token in your Identity along with Username and pass these two to your child services. Now for this approach to work, child service has to validate your new generated token, for which you should have a token Service which can create token by validating username and password and also which can validate token along with username.
Personally I would go for approach 2, but it will introduce new overheads.

Categories

Resources