dotnet / aspnetcore

ASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.
https://asp.net
MIT License
35.48k stars 10.03k forks source link

How to disable registration for public side of a web application? #15207

Closed Aleksej-Shherbak closed 5 years ago

Aleksej-Shherbak commented 5 years ago

I have simple web application on .NET Core 2.2. I would like to have only authorization for public side of application. You can ask me "where I will take new users for my system if I will have only authorization?". I would like to create users manually. With my point of view, this task is very simple. Any web-framework has some functional for this. But I can't find civilized way to implement it for .NET Core!

So, actually I have pretty ugly way to make it. I just added this middleware:

Sample form Startup.cs file

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            // disable registration if not Local environment
            app.Use(async (context, next) =>
            {
                if (env.IsEnvironment("Local"))
                {
                    await next.Invoke();
                }
                else
                {
                    if (context.Request.Path.ToString().Contains("Register") ||
                        context.Request.Path.ToString().Contains("ForgotPassword"))
                    {
                        await context.Response.WriteAsync("Registration is closed");
                    }
                    else
                    {
                        await next.Invoke();
                    }    
                }
            });

As you can see, if the environment is local - I can register a new user. Else - registration is closed.

I knew that this way is bad and I had started research how to make it better. My plan was overwrite AcountController and user additional conditions for registration method. I know that there is this instrument ms documentation. It can generate identity scaffolding. But it can't generate AccountController! Only views. I tried to make AccountController manually, but without success. I will show you:

    public class AccountController : Controller
    {
        private readonly UserManager<IdentityUser> _userManager;
        private readonly SignInManager<IdentityUser> _signInManager;

        public AccountController(SignInManager<IdentityUser> signInManager, UserManager<IdentityUser> userManager)
        {
            _signInManager = signInManager;
            _userManager = userManager;
        }

        [HttpGet]
        public IActionResult Login()
        {
            return NotFound();
        }
    }

I expected that the above code switch off login and makes for me 404 instead of login. But login works.

This task is easy: to make web application with authorization but without registration (only developer can add new users). How to make it for .Net Core?

I put here Identity settings:

services.AddDefaultIdentity<IdentityUser>()
                .AddDefaultUI(UIFramework.Bootstrap4)
                .AddEntityFrameworkStores<ApplicationDbContext>();
javiercn commented 5 years ago

@Aleksej-Shherbak Thanks for contacting us.

I would recommend you override the pages in the UI that contain links to register (I think its only login) and the register page, and on the register page you simply redirect to the login page.

That can be done by creating a page in /Areas/Identity/Account/Register.cshtml that will override the one contained in the library, and from there you can simply redirect to the login page.

Aleksej-Shherbak commented 5 years ago

@javiercn Thank you for answer. I will show you my solution. Could you tell me is it secure way?

This code in the Register.cshtml.cs file

        public IActionResult OnGet()
        {
            if (!_hostingEnvironment.IsEnvironment("Local"))
            {
                return RedirectToPage("/Account/Login");
            }

            return RedirectToPage("/Account/Register");
        }

        public async Task<IActionResult> OnPostAsync(string returnUrl = null)
        {
            returnUrl = returnUrl ?? Url.Content("~/");
            if (ModelState.IsValid && _hostingEnvironment.IsEnvironment("Local"))
            {
                var user = new IdentityUser { UserName = Input.Email, Email = Input.Email };
                var result = await _userManager.CreateAsync(user, Input.Password);
                if (result.Succeeded)
                {
                    _logger.LogInformation("User created a new account with password.");

                    var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
                    var callbackUrl = Url.Page(
                        "/Account/ConfirmEmail",
                        pageHandler: null,
                        values: new { userId = user.Id, code = code },
                        protocol: Request.Scheme);

                    await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
                        $"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");

                    await _signInManager.SignInAsync(user, isPersistent: false);
                    return LocalRedirect(returnUrl);
                }
                foreach (var error in result.Errors)
                {
                    ModelState.AddModelError(string.Empty, error.Description);
                }
            }

            // If we got this far, something failed, redisplay form
            return Page();
        }

As you can see I have changed if (ModelState.IsValid) to if (ModelState.IsValid && _hostingEnvironment.IsEnvironment("Local")) and I have changed public IActionResult OnGet().

What I have as the result? I can create a new user if my environment is local. Could you tell me is it secure way? I mean no one will be able to make registration from web?

javiercn commented 5 years ago

@Aleksej-Shherbak I would not include the functionality at all if you can avoid it.

If you mean to be able to register users during local development, you can alternatively write a very simple console app that connects to the DB and populates the initial list of users. After all, as you mention registration is not part of your app, so if you reduce the surface attack area that's definitely better.

As I said, for development purposes, referencing your web app from a command-line app, configuring the service container and populating the Db with a list of users seems like a better approach in my opinion.

Hope that helps.

I'm closing this issue as the original question has been answered.

Aleksej-Shherbak commented 5 years ago

@javiercn thank you for answer! It was useful!