Search code examples
asp.netasp.net-mvcasp.net-web-apiweb-applicationsasp.net-identity-2

I want web (MVC) and API on same database


I have a database, need web interface for administration and management (number of transactions, billing, and other administration) and to serve data from database (products) "manually" and API to serve data (products) to other bigger clients. All secured by SSL and https obviously.

I made a asp.net MVC 5 app (business logic and administration) and wanted to implement API (noob in API) for delivering data to users.

Have no idea how to implement security from MVC to API(same database).

The app is small and I can rewrite it. I'm thinking to try with core, but fear that i will be stuck with same problem.

Concrete question: What approach do I take and weather it should be in MVC 5 generation or .core (MVC 6) to be able to use one database for data, users and their authorizations?

(pushing everything true API is something I would like to avoid)


Solution

  • Ok, my project is done. I moved ahead on MVC 5.

    (I apologize to you perfectionists, but I don't have the time now to strip unnecessary thing so I dumped whole files as they are :)

    1st approach - abandoned

    First I tried designing it as is recommended true the internet: .MVC solution, .DB for database and .API solution.

    Conclusion - A lot of problems wit authentication and entity framework. At the end I abandoned this approach

    2nd and successful approach

    Just one solution .MVC

    True NuGet installed .net Api, used integrated authorization extended using few tutorials (not single one worked). Note that I use ASP.NET Identity 2.0 Extending Identity Models and Using Integer Keys Instead of Strings and Implementing HTTPS Everywhere in ASP.Net MVC application.

    Here are modifications and addons:

    App_Start -> IdentityConfig.cs

    public class ApplicationUserManager : UserManager<ApplicationUser, int>
    {
        // *** ADD INT TYPE ARGUMENT TO CONSTRUCTOR CALL:
        public ApplicationUserManager(IUserStore<ApplicationUser, int> store)
            : base(store)
        {
        }
    
        public static ApplicationUserManager Create(
            IdentityFactoryOptions<ApplicationUserManager> options,
            IOwinContext context)
        {
            // *** PASS CUSTOM APPLICATION USER STORE AS CONSTRUCTOR ARGUMENT:
            var manager = new ApplicationUserManager(
                new ApplicationUserStore(context.Get<ApplicationDbContext>()));
    
            // Configure validation logic for usernames
    
            // *** ADD INT TYPE ARGUMENT TO METHOD CALL:
            manager.UserValidator = new UserValidator<ApplicationUser, int>(manager)
            {
                AllowOnlyAlphanumericUserNames = false,
                RequireUniqueEmail = true
            };
    
            // Configure validation logic for passwords
            manager.PasswordValidator = new PasswordValidator
            {
                RequiredLength = 6,
                RequireNonLetterOrDigit = false,
                RequireDigit = true,
                RequireLowercase = true,
                RequireUppercase = true,
            };
    
    
                // other code removed for brevity      
            manager.UserLockoutEnabledByDefault = Convert.ToBoolean(ConfigurationManager.AppSettings["UserLockoutEnabledByDefault"].ToString());
            manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(Double.Parse(ConfigurationManager.AppSettings["DefaultAccountLockoutTimeSpan"].ToString()));
            manager.MaxFailedAccessAttemptsBeforeLockout = Convert.ToInt32(ConfigurationManager.AppSettings["MaxFailedAccessAttemptsBeforeLockout"].ToString());
    
    
            // Register two factor authentication providers. 
            // This application uses Phone and Emails as a step of receiving a 
            // code for verifying the user You can write your own provider and plug in here.
    
            // *** ADD INT TYPE ARGUMENT TO METHOD CALL:
            //manager.RegisterTwoFactorProvider("PhoneCode",
            //  new PhoneNumberTokenProvider<ApplicationUser, int>
            //  {
            //      MessageFormat = "Your security code is: {0}"
            //  });
    
            //// *** ADD INT TYPE ARGUMENT TO METHOD CALL:
            //manager.RegisterTwoFactorProvider("EmailCode",
            //  new EmailTokenProvider<ApplicationUser, int>
            //  {
            //      Subject = "SecurityCode",
            //      BodyFormat = "Your security code is {0}"
            //  });
    
            //manager.EmailService = new EmailService();
            //manager.SmsService = new SmsService();
            var dataProtectionProvider = options.DataProtectionProvider;
            if (dataProtectionProvider != null)
            {
                // *** ADD INT TYPE ARGUMENT TO METHOD CALL:
                manager.UserTokenProvider =
                    new DataProtectorTokenProvider<ApplicationUser, int>(
                        dataProtectionProvider.Create("ASP.NET Identity"));
            }
            return manager;
        }
    }
    
    
    // PASS CUSTOM APPLICATION ROLE AND INT AS TYPE ARGUMENTS TO BASE:
    public class ApplicationRoleManager : RoleManager<ApplicationRole, int>
    {
        // PASS CUSTOM APPLICATION ROLE AND INT AS TYPE ARGUMENTS TO CONSTRUCTOR:
        public ApplicationRoleManager(IRoleStore<ApplicationRole, int> roleStore)
            : base(roleStore)
        {
        }
    
        // PASS CUSTOM APPLICATION ROLE AS TYPE ARGUMENT:
        public static ApplicationRoleManager Create(
            IdentityFactoryOptions<ApplicationRoleManager> options, IOwinContext context)
        {
            return new ApplicationRoleManager(
                new ApplicationRoleStore(context.Get<ApplicationDbContext>()));
        }
    }
    
    
    public class EmailService : IIdentityMessageService
    {
        public Task SendAsync(IdentityMessage message)
        {
            // Plug in your email service here to send an email.
            return Task.FromResult(0);
        }
    }
    
    
    public class SmsService : IIdentityMessageService
    {
        public Task SendAsync(IdentityMessage message)
        {
            // Plug in your sms service here to send a text message.
            return Task.FromResult(0);
        }
    }
    
    //This is useful if you do not want to tear down the database each time you run the application.
    //public class ApplicationDbInitializer : DropCreateDatabaseAlways<ApplicationDbContext>
    //This example shows you how to create a new database if the Model changes
    public class ApplicationDbInitializer : DropCreateDatabaseIfModelChanges<ApplicationDbContext>
    {
        protected override void Seed(ApplicationDbContext context)
        {
            //InitializeIdentityForEF(context); //- Do not Seed - IGOR
            //base.Seed(context);
        }
    
        //Create User=Admin@Admin.com with password=Admin@123456 in the Admin role        
        //public static void InitializeIdentityForEF(ApplicationDbContext db)
        //{
        //  var userManager = HttpContext.Current.GetOwinContext().GetUserManager<ApplicationUserManager>();
        //  var roleManager = HttpContext.Current.GetOwinContext().Get<ApplicationRoleManager>();
        //  const string name = "igor@email.mail";
        //  const string password = "LolLol1";
        //  const string roleName = "lol";
    
        //  //Create Role Admin if it does not exist
        //  var role = roleManager.FindByName(roleName);
        //  if (role == null)
        //  {
        //      role = new ApplicationRole(roleName);
        //      var roleresult = roleManager.Create(role);
        //  }
    
        //  var user = userManager.FindByName(name);
        //  if (user == null)
        //  {
        //      user = new ApplicationUser { UserName = name, Email = name };
        //      var result = userManager.Create(user, password);
        //      result = userManager.SetLockoutEnabled(user.Id, false);
        //  }
    
        //  // Add user admin to Role Admin if not already added
        //  var rolesForUser = userManager.GetRoles(user.Id);
        //  if (!rolesForUser.Contains(role.Name))
        //  {
        //      var result = userManager.AddToRole(user.Id, role.Name);
        //  }
        //}
    }
    
    
    public class ApplicationSignInManager : SignInManager<ApplicationUser, int>
    {
        public ApplicationSignInManager(ApplicationUserManager userManager, IAuthenticationManager authenticationManager) :
            base(userManager, authenticationManager)
        { }
    
        public override Task<ClaimsIdentity> CreateUserIdentityAsync(ApplicationUser user)
        {
            return user.GenerateUserIdentityAsync((ApplicationUserManager)UserManager);
        }
    
        public static ApplicationSignInManager Create(IdentityFactoryOptions<ApplicationSignInManager> options, IOwinContext context)
        {
            return new ApplicationSignInManager(context.GetUserManager<ApplicationUserManager>(), context.Authentication);
        }
    }
    

    App_Start -> Startup.Auth.cs

    public partial class Startup
    {
        public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }
    
        public static string PublicClientId { get; private set; }
    
    
        public void ConfigureAuth(IAppBuilder app)
        {
            // Configure the db context, user manager and role manager to use a single instance per request
            app.CreatePerOwinContext(ApplicationDbContext.Create);
            app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
            app.CreatePerOwinContext<ApplicationRoleManager>(ApplicationRoleManager.Create);
            app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
    
            // Enable the application to use a cookie to store information for the signed in user
            // and to use a cookie to temporarily store information about a user logging in with a third party login provider
            // Configure the sign in cookie
            app.UseCookieAuthentication(new CookieAuthenticationOptions
            {
                AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
                LoginPath = new PathString("/Account/Login"),
                Provider = new CookieAuthenticationProvider
                {
                    // Enables the application to validate the security stamp when the user logs in.
                    // This is a security feature which is used when you change a password or add an external login to your account.  
                    OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser, int>(
                        validateInterval: TimeSpan.FromMinutes(2880),
                        regenerateIdentityCallback: (manager, user) => user.GenerateUserIdentityAsync(manager),
                            // Need to add THIS line because we added the third type argument (int) above:
                            getUserIdCallback: (claim) => int.Parse(claim.GetUserId()))
                }
            });
            app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
    
            // Enables the application to temporarily store user information when they are verifying the second factor in the two-factor authentication process.
            app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));
    
            // Enables the application to remember the second login verification factor such as phone or email.
            // Once you check this option, your second step of verification during the login process will be remembered on the device where you logged in from.
            // This is similar to the RememberMe option when you log in.
            app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
    
            // Uncomment the following lines to enable logging in with third party login providers
            //app.UseMicrosoftAccountAuthentication(
            //    clientId: "",
            //    clientSecret: "");
    
            //app.UseTwitterAuthentication(
            //   consumerKey: "",
            //   consumerSecret: "");
    
            //app.UseFacebookAuthentication(
            //   appId: "",
            //   appSecret: "");
    
            //app.UseGoogleAuthentication(
            //    clientId: "",
            //    clientSecret: "");
    
            // Configure the application for OAuth based flow
            PublicClientId = "self";
            OAuthOptions = new OAuthAuthorizationServerOptions
            {
                TokenEndpointPath = new PathString("/Token"),
                Provider = new ApplicationOAuthProvider(PublicClientId),
                AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"), //TODO - makni ovo
                AccessTokenExpireTimeSpan = TimeSpan.FromHours(1),
                // In production mode set AllowInsecureHttp = false
                AllowInsecureHttp = true
            };
    
            // Enable the application to use bearer tokens to authenticate users
            app.UseOAuthBearerTokens(OAuthOptions);
        }
    }
    //public partial class Startup
    //{
    //  public void ConfigureAuth(IAppBuilder app)
    //  {
    //      // Enable the application to use a cookie to store information for the signed in user
    //      app.UseCookieAuthentication(new CookieAuthenticationOptions
    //      {
    //          ExpireTimeSpan = TimeSpan.FromHours(24),
    //          CookieSecure = CookieSecureOption.Never,
    //          CookieHttpOnly = false,
    //          SlidingExpiration = true,
    //          AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
    //          LoginPath = new PathString("/Account/Login")
    //      });
    //      // Use a cookie to temporarily store information about a user logging in with a third party login provider
    //      app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
    //  }
    //}
    

    App_Start -> WebApiConfig.cs

    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // TODO: Add any additional configuration code.
    
            // Web API routes
            config.MapHttpAttributeRoutes();
    
            //config.Routes.MapHttpRoute(
            //  name: "getkey",
            //  routeTemplate: "api/ApiKeys/Get/{term}"
    
            //);
    
            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
    
            // WebAPI when dealing with JSON & JavaScript!
            // Setup json serialization to serialize classes to camel (std. Json format)
            var formatter = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
            formatter.SerializerSettings.ContractResolver =
                new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver();
    
            // make all web-api requests to be sent over https
            config.MessageHandlers.Add(new EnforceHttpsHandler());
        }
    }
    

    MySysAdmin controller that I use for initial insert and edit of roles and initial user.

    public SysAdminController(ApplicationUserManager userManager,
            ApplicationRoleManager roleManager)
        {
            UserManager = userManager;
            RoleManager = roleManager;
        }
    
        private ApplicationUserManager _userManager;
        public ApplicationUserManager UserManager
        {
            get
            {
                return _userManager ?? HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
            }
            set
            {
                _userManager = value;
            }
        }
    
        private ApplicationRoleManager _roleManager;
        public ApplicationRoleManager RoleManager
        {
            get
            {
                return _roleManager ?? HttpContext.GetOwinContext().Get<ApplicationRoleManager>();
            }
            private set
            {
                _roleManager = value;
            }
        }
    
        public ActionResult RoleIndex()
        {
            return View(RoleManager.Roles);
        }
    
        public ActionResult RoleCreate()
        {
            return View();
        }
    
        [HttpPost]
        public async Task<ActionResult> RoleCreate(SysAdminVM.RoleViewModel roleViewModel)
        {
            if (ModelState.IsValid)
            {
                // Use ApplicationRole, not IdentityRole:
                var role = new ApplicationRole(roleViewModel.Name);
                var roleresult = await RoleManager.CreateAsync(role);
                if (!roleresult.Succeeded)
                {
                    ModelState.AddModelError("", roleresult.Errors.First());
                    return View();
                }
                return RedirectToAction("RoleIndex");
            }
            return View();
        }
    
        public async Task<ActionResult> RoleEdit(int id)
        {
            if (id > 0)
            {
                var role = await RoleManager.FindByIdAsync(id);
                if (role == null)
                {
                    return HttpNotFound();
                }
                SysAdminVM.RoleViewModel roleModel = new SysAdminVM.RoleViewModel { Id = role.Id, Name = role.Name };
                return View(roleModel);
            }
            return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
        }
    
        [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<ActionResult> RoleEdit([Bind(Include = "Name,Id")] SysAdminVM.RoleViewModel roleModel)
        {
            if (ModelState.IsValid)
            {
                var role = await RoleManager.FindByIdAsync(roleModel.Id);
                role.Name = roleModel.Name;
                await RoleManager.UpdateAsync(role);
                return RedirectToAction("RoleIndex");
            }
            return View();
        }
    
        [AllowAnonymous]
        public async Task<ActionResult> Initialize()
        {
            if (db.App.Where(x => x.Name.Contains("Initialize")).FirstOrDefault() == null)
            {
                await InitRoleCreate();
                await InitUser();
                db.App.Add(
                    new App { Name = "Initialize", Val = "true" }
                );
                db.SaveChanges();
                return View();
            }
            return HttpNotFound();
        }
    
        private async Task InitRoleCreate()
        {
            var model = new List<string>()
        {
            "SysAdmin",
            "Admin",
            "User",
        };
            foreach (var item in model)
            {
                var role = new ApplicationRole(item);
                await RoleManager.CreateAsync(role);
            }
        }
    
        private async Task InitUser()
        {
            var user = new ApplicationUser
            {
                UserName = "HerGiz",
                Email = "hergiz@outlook.com",
                Name = "Igor Hermanović",
                Contact = "098 185 3131",
                TwoFactorEnabled = false,
                LockoutEnabled = true,
                EmailConfirmed = true
            };
            var adminResult = await UserManager.CreateAsync(user, "W7xtc2ywfb");
            await UserManager.AddToRolesAsync(user.Id, "SysAdmin");
        }
    }
    

    Entire API part that I need - controller and out of the box login (that is berried somewhere):

    [Authorize]
    public class ApiKeysController : ApiController
    {
        [Authorize]
        [Route("api/getkey/{term}")]
        public ShowFullKeyVM Get(string term)
        {
            if (User.Identity.IsAuthenticated == true)
            {
                if (!string.IsNullOrWhiteSpace(term) && (term.Length == 15 || term.Length == 16))
                {
                    var lKey = new LKey();
                    var vm = lKey.Search(term);
                    if (vm != null)
                    {
                        return vm;
                    }
                }
                return new ShowFullKeyVM() { Error = "IMEI either is not valid :(", SearchIMEI = term };
            }
            return new ShowFullKeyVM() { Error = "Not Authenticated!!!", SearchIMEI = term };
        }
    }
    

    Global.asax

    protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
    
            GlobalConfiguration.Configure(WebApiConfig.Register);
    
            MvcHandler.DisableMvcResponseHeader = true;
    
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);
    
            App_Start.AutoMapperConfig.DefineMaps();
    
            ModelBinders.Binders.Add(typeof(decimal), new Extensions.DecimalModelBinder());
            ModelBinders.Binders.Add(typeof(decimal?), new Extensions.DecimalModelBinder());
        }
    

    Web.config

    <appSettings>
        <add key="UserLockoutEnabledByDefault" value="true" />
        <add key="DefaultAccountLockoutTimeSpan" value="30" />
        <add key="MaxFailedAccessAttemptsBeforeLockout" value="4" />
     </appSettings>