Click here to Skip to main content
15,886,519 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
See more:
Hi. I'm using DirectoryServices.AccountManagement for authorizing users to log into my web application. The code I used is like the following for authorizing and it can correctly authorize active directory users. As I read a variety of articles, I need to write something like [Authorize(Users="DOMAIN\UserName")] on top my controller classes in order to specify that which controller, the user has access. But now my problem is what should I write in the body of "if (ctx.ValidateCredentials(userName, password)" after the user' authorization is successful. I wrote something like "return this.RedirectToLocal(returnUrl);" but after clicking the login button, although in the console it prints "You are logged in" but still the login form remains. I appreciate if anyone can suggest me a solution accordingly.

 [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> Signin(SigninViewModel model, string returnUrl = null)
        {
            this.ViewData["ReturnUrl"] = returnUrl;

            using (PrincipalContext ctx = new PrincipalContext(ContextType.Domain, "tehran.iri"))
            {
                // validate the user's credentials
                //var result = ctx.ValidateCredentials(model.UserName, model.Password);
                //    try { 
                if (ctx.ValidateCredentials(model.UserName, model.Password))
                {
                    // credentials are OK --> allow user in
                    Debug.Writeline("You are logged in");
                    return this.RedirectToLocal(returnUrl);

                }
                else
                {
                    this.TempData["ErrorMessage"] = "The username and/or password are incorrect!";

                    return this.View(model);
                    // credentials aren't OK --> send back error message
                }
       }
}


What I have tried:

HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> Signin(SigninViewModel model, string returnUrl = null)
        {
            this.ViewData["ReturnUrl"] = returnUrl;

            using (PrincipalContext ctx = new PrincipalContext(ContextType.Domain, "tehran.iri"))
            {
                // validate the user's credentials
                //var result = ctx.ValidateCredentials(model.UserName, model.Password);
                //    try { 
                if (ctx.ValidateCredentials(model.UserName, model.Password))
                {
                    // credentials are OK --> allow user in
                    Debug.Writeline("You are logged in");
                    return this.RedirectToLocal(returnUrl);

                }
                else
                {
                    this.TempData["ErrorMessage"] = "The username and/or password are incorrect!";

                    return this.View(model);
                    // credentials aren't OK --> send back error message
                }
       }
}
Posted
Updated 29-Apr-20 4:39am

The problem is that you haven't set anything to indicate that the user is signed in.

You validate the credentials and redirect to the returnUrl, but that redirected request will be anonymous. The Authorize filter will see an anonymous request for a resource which requires authentication, and will redirect you back to the login page.

The simplest approach would probably be to store the authenticated username in the session, and use that in the AuthenticateRequest event to set the authenticated user for the request. For example:

Helper class:
C#
public static class AuthenticationHelper
{
    private const string SessionKey = "AuthenticationHelper.UserName";
    
    public static void MarkAsAuthenticated(this HttpSessionStateBase session, string authenticatedUserName)
    {
        session[SessionKey] = authenticatedUserName;
    } 
    
    public static IPrincipal GetAuthenticatedUser(this HttpSessionState session)
    {
        string authenticatedUserName = (string)session[SessionKey];
        if (string.IsNullOrEmpty(authenticatedUserName)) return null;
        return new GenericPrincipal(new GenericIdentity(authenticatedUserName), Array.Empty<string>());
    }
}
Sign in action:
C#
if (ctx.ValidateCredentials(model.UserName, model.Password))
{
    // credentials are OK --> allow user in
    Debug.Writeline("You are logged in");
    Session.MarkAsAuthenticated(model.UserName);
    return RedirectToLocal(returnUrl);
}
Global.asax.cs:
C#
protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
    IPrincipal user = Session.GetAuthenticatedUser();
    if (user != null) Context.User = user;
}
If you're using [Authorize(Users = "...")], the usernames will need to match what the user entered when they signed in.

NB: It's usually preferable to use roles instead of users, so that you don't have to recompile your code to change who has access to what.

NB2: If everyone who signs in can access all actions, and you don't need to restrict specific actions to specific users, you can just use [Authorize] instead.



Edit: For ASP.NET Core, you'd need to use custom middleware instead of the Global.asax file.
Migrate HTTP handlers and modules to ASP.NET Core middleware | Microsoft Docs[^]

You'll also need to update the helper to account for the new ISession interface.

Helper class:
C#
public static class AuthenticationHelper
{
    private const string SessionKey = "AuthenticationHelper.UserName";
    
    public static void MarkAsAuthenticated(this Microsoft.AspNetCore.Http.ISession session, string authenticatedUserName)
    {
        session.SetString(SessionKey, authenticatedUserName);
    } 
    
    public static ClaimsPrincipal GetAuthenticatedUser(this Microsoft.AspNetCore.Http.ISession session)
    {
        string authenticatedUserName = session.GetString(SessionKey);
        if (string.IsNullOrEmpty(authenticatedUserName)) return null;
        return new GenericPrincipal(new GenericIdentity(authenticatedUserName), Array.Empty<string>());
    }
}
Middleware:
C#
public class CustomAuthenticationMiddleware
{
    private readonly RequestDelegate _next;
    
    public CustomAuthenticationMiddleware(RequestDelegate next)
    {
        _next = next;
    }
    
    public async Task Invoke(HttpContext context)
    {
        ClaimsPrincipal user = context.Session.GetAuthenticatedUser();
        if (user != null) context.User = user;
        await _next(context);
    }
}

public static class CustomAuthenticationMiddlewareExtensions
{
    public static IApplicationBuilder UseCustomAuthentication(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<CustomAuthenticationMiddleware>();
    }    
}
Startup:
C#
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    ...
    app.UseCustomAuthentication();
    app.UseAuthorization();
    ...
}
Make sure you add the authentication / authorization lines after UseRouting, but before UseEndpoints.

You'll also need to call AddSession in the ConfigureServices method, and UseSession in the Configure method, as described in the documentation:
Session in ASP.NET Core | Microsoft Docs[^]

The UseSession call will need to appear before the UseCustomAuthentication call.
 
Share this answer
 
v4
Comments
ElenaRez 30-Apr-20 2:57am    
Thank you very much for your helpful reply. I'm implementing asp.net cor 3.1 and I don't have Global.asax.cs file. Instead I have appsettings.jason and startup.cs. How can I use your Global.asax file in my own project?
Richard Deeming 30-Apr-20 5:49am    
For ASP.NET Core, you'll need to use a custom middleware instead:
Migrate HTTP handlers and modules to ASP.NET Core middleware | Microsoft Docs[^]
Richard Deeming 30-Apr-20 15:01pm    
GenericPrincipal[^] is a ClaimsPrincipal, so you just need to change the return type of the extension method, and the variable type in the middleware.
ElenaRez 1-May-20 23:36pm    
I pasted what you said in my code and now the user could login to my website. In my website, I have 4 controllers and on top of each, I added [Authorize] as you suggested in order to let all the user to work with them. But now the problem is, when the user click the related menu icon, all those pages shows white pages. I appreciate if you guide me about the problem
This is going to take a little more debugging... I would suggest adding in this line to check the value of returnUrl which has a default value of null in the method declaration; and it's kinda hard to redirect there
C#
if (ctx.ValidateCredentials(model.UserName, model.Password))
{
	// credentials are OK --> allow user in
	Debug.Writeline("You are logged in");
	Debug.Writeline(string.Format("Redirection to {0}", returnUrl);  // add this
	return this.RedirectToLocal(returnUrl);
}
 
Share this answer
 
Comments
ElenaRez 8-May-20 23:30pm    
Thank you for your reply. I pasted what you suggested and after running the project, there was shown "Redirection to /" to me in debug console. How can I fix this? Even I modified my signin method like the following, but there wasn't any change for Redirection result in debug console:

public async Task<iactionresult> Signin(SigninViewModel model, string returnUrl = null)

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900