0

Here is startup.cs i have configured okta openid connect as per below. please review it

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        IConfigurationRoot configuration = new ConfigurationBuilder()
            .SetBasePath(Directory.GetCurrentDirectory())
            .AddJsonFile("appsettings.json")
            .Build();
        services.AddAuthentication(options =>
            {
                options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
            })
            .AddCookie()
            .AddOktaMvc(new OktaMvcOptions
            {
                // Replace these values with your Okta configuration
                OktaDomain = Configuration.GetValue<string>("AppSettings:OktaDomain"),
                ClientId = Configuration.GetValue<string>("AppSettings:ClientId"),
                ClientSecret = Configuration.GetValue<string>("AppSettings:ClientSecret"),
                Scope = new List<string> { "openid", "profile", "email" },
            });

        //services.AddAuthorization();


        services.Configure<FormOptions>(x => x.ValueCountLimit = 8192);

        services.Configure<CookiePolicyOptions>(options =>
        {
            options.CheckConsentNeeded = context => false;
            options.MinimumSameSitePolicy = SameSiteMode.None;
        });


        services.AddOptions();

        services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));


        var lockoutOptions = new LockoutOptions()
        {
            AllowedForNewUsers = true,
            DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5),
            MaxFailedAccessAttempts = 3
        };

        services.AddIdentity<ApplicationUser, IdentityRole<Guid>>(options =>
            {
                options.Password.RequireDigit = true;
                options.Password.RequiredLength = 6;
                options.Password.RequiredUniqueChars = 0;
                options.Password.RequireLowercase = false;
                options.Password.RequireUppercase = false;
                options.Password.RequireNonAlphanumeric = false;
                options.Lockout = lockoutOptions;
            })
            .AddEntityFrameworkStores<AmpCoreContext>().AddDefaultTokenProviders();

        services.ConfigureApplicationCookie(options =>
        {
            //     Cookie settings
            options.Cookie.HttpOnly = true;
            options.ExpireTimeSpan = TimeSpan.FromMinutes(20);
            options.LoginPath = new PathString("/Identity/Account/Login");
            options.AccessDeniedPath = "/Identity/Account/AccessDenied";
            options.ReturnUrlParameter = CookieAuthenticationDefaults.ReturnUrlParameter;
            options.SlidingExpiration = true;
        });
        services.AddSession(options => { options.IdleTimeout = TimeSpan.FromMinutes(20); });


        services
            .AddControllersWithViews()
            .SetCompatibilityVersion(CompatibilityVersion.Version_3_0)
            // Maintain property names during serialization. See:
            // https://github.com/aspnet/Announcements/issues/194
            .AddNewtonsoftJson(options => options.SerializerSettings.ContractResolver = new DefaultContractResolver());

        services.AddRazorPages().AddRazorRuntimeCompilation();

        services.AddHttpContextAccessor();
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        var culture = CultureInfo.CreateSpecificCulture("en-US");
        var dateformat = new DateTimeFormatInfo
        {
            ShortDatePattern = "MM/dd/yyyy",
            LongDatePattern = "MM/dd/yyyy hh:mm:ss tt"
        };
        culture.DateTimeFormat = dateformat;

        var supportedCultures = new[] { culture };

        app.UseRequestLocalization(new RequestLocalizationOptions
        {
            DefaultRequestCulture = new RequestCulture(culture),
            SupportedCultures = supportedCultures,
            SupportedUICultures = supportedCultures
        });
        using (var serviceScope = app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope())
        {
            try
            {
                var context = serviceScope.ServiceProvider.GetRequiredService<AmpCoreContext>();
                var customers = context.Customer.ToListAsync().Result;
                string output =
                    Newtonsoft.Json.JsonConvert.SerializeObject(customers, Newtonsoft.Json.Formatting.Indented);
                File.WriteAllText("tenants.json", output);
            }
            catch (Exception)
            {
                throw;
            }
        }

        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
            // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
            app.UseHsts();
        }


        app.UseHttpsRedirection();
        app.UseStaticFiles();

        app.UseRouting();
        app.UseAuthentication();
        app.UseAuthorization();

        //app.UseCookiePolicy();
        app.UseSession();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllerRoute(
                name: "default",
                pattern: "{controller=Home}/{action=Index}/{id?}");

            endpoints.MapRazorPages();
        });
    }
}

and here is my Account controller , on login button click i redirect it to account controller singin method. but it always call in loop. please check code below

public class AccountController : Controller
{
    public IActionResult SignIn()
    {
        if (!HttpContext.User.Identity.IsAuthenticated)
        {
            return Challenge(OktaDefaults.MvcAuthenticationScheme);
        }

        return RedirectToAction("Index", "Home");
    }

    [HttpPost]
    public IActionResult SignOut()
    {
        return new SignOutResult(
            new[]
            {
                OktaDefaults.MvcAuthenticationScheme,
                CookieAuthenticationDefaults.AuthenticationScheme,
            },
            new AuthenticationProperties { RedirectUri = "/Home/" });
    }
}

It redirect to okta , okta return but HttpContext.User.Identity.IsAuthenticated is always false in account controller , it is is infinite loop.

Here is home controller

using Microsoft.AspNetCore.Mvc;
using System.Diagnostics;
using Project.Common;
using System;
using Microsoft.AspNetCore.Identity;
using Project.Data.Identity;
using Kendo.Mvc.Extensions;
using Kendo.Mvc.UI;
using Microsoft.AspNetCore.DataProtection;
using Project.Application.Interfaces;
using Project.Application.ViewModel;
using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Data;
using Microsoft.AspNetCore.Http;
using Project.Data.Data;
using ErrorViewModel = Project.Models.ErrorViewModel;
using System.Linq;
using Okta.AspNetCore;
using Microsoft.AspNetCore.Authorization;

namespace Project.Controllers
{
    [Authorize]
    public class HomeController : BaseController
    {
        private readonly UserManager<ApplicationUser> _userManager;
        private readonly ICustomerService _customerService;
        private IPasswordHasher<ApplicationUser> _passwordHasher;
        private readonly IFieldService _fieldService;
        private IModuleService _moduleService;
        private readonly IHttpContextAccessor _httpContextAccessor;
        private ISession _session => _httpContextAccessor.HttpContext.Session;
        private readonly IUserAccessSecurityService _userAccessSecurityService;
        private readonly IGroupService _groupService;
        private readonly Context _context;
        private IDataProtectionProvider _provider;
        private readonly IUserSessionLogService _userSessionLogService;
        public HomeController(UserManager<ApplicationUser> userManager, ICustomerService customerService, IPasswordHasher<ApplicationUser> passwordHasher, IFieldService fieldService,
            IModuleService moduleService, IUserAccessSecurityService userAccessSecurityService, IGroupService groupService, Context context, IHttpContextAccessor httpContextAccessor,
            IDataProtectionProvider provider, SignInManager<ApplicationUser> signInManager, IUserSessionLogService userSessionLogService) : base(signInManager, userAccessSecurityService, provider, userSessionLogService)
        {
            _userManager = userManager;
            _customerService = customerService;
            _passwordHasher = passwordHasher;
            _fieldService = fieldService;
            _moduleService = moduleService;
            _userAccessSecurityService = userAccessSecurityService;
            _groupService = groupService;
            _context = context;
            _httpContextAccessor = httpContextAccessor;
            _provider = provider;
            _userSessionLogService = userSessionLogService;
        }
        public IActionResult unauthorised()
        {
            return View("~/Views/Home/AccessDenied.cshtml");
        }
        [Authorize]
        public IActionResult Index()
        {
            if (HttpContext.Session.GetObjectFromJson<Guid>("CurrentUserId") != Guid.Empty)
            {
                var Fk_Customer = HttpContext.Session.GetObjectFromJson<int>("CurrentCustomer");
                var FK_User = HttpContext.Session.GetObjectFromJson<Guid>("CurrentUserId");
                bool hasGroup = false;

                DashBoardViewModel dashBoardViewModel = new DashBoardViewModel();
                List<ViewUserGroup> viewUserGroup = new List<ViewUserGroup>();
                ApplicationUser appUsers = _context.Users.SingleOrDefault(x => x.Id == FK_User);
                bool isSuperAdmin = _context.UserType.Where(x => x.ID == appUsers.FK_UserType && x.Type.ToLower() == "super admin").Any();

                bool isAdmin = _context.UserType.Where(x => x.ID == appUsers.FK_UserType && x.Type.ToLower() == "company admin").Any();

                viewUserGroup = _userAccessSecurityService.GetUserGroupByUser(Fk_Customer, FK_User);

                if (viewUserGroup.Count > 0)
                {
                    if (viewUserGroup[0].Fk_Group != 0 && viewUserGroup[0].PK_Group != 0)
                        hasGroup = true;
                }
                ViewBag.hasGroup = hasGroup;
                ViewBag.isSuperAdmin = isSuperAdmin;

                DataTable dtPendingAssetCount = _fieldService.GetPendingAssetCount(Convert.ToInt32(Fk_Customer), (isAdmin ? true : (isSuperAdmin) ? true : false), FK_User);
                if (dtPendingAssetCount != null && dtPendingAssetCount.Rows.Count > 0)
                {
                    dashBoardViewModel.AccountReminder.Pending_Valuation_Count = dtPendingAssetCount.Rows[0]["Pending_Valuation_Count"] != DBNull.Value ? Convert.ToInt32(dtPendingAssetCount.Rows[0]["Pending_Valuation_Count"]) : 0;
                    dashBoardViewModel.AccountReminder.Pending_New_Assets_Count = dtPendingAssetCount.Rows[0]["Pending_New_Assets_Count"] != DBNull.Value ? Convert.ToInt32(dtPendingAssetCount.Rows[0]["Pending_New_Assets_Count"]) : 0;
                    dashBoardViewModel.AccountReminder.Pending_Change_Request_Count = dtPendingAssetCount.Rows[0]["Pending_Change_Request_Count"] != DBNull.Value ? Convert.ToInt32(dtPendingAssetCount.Rows[0]["Pending_Change_Request_Count"]) : 0;
                    dashBoardViewModel.AccountReminder.Pending_New_Assets_State_Count = dtPendingAssetCount.Rows[0]["Pending_New_Assets_State_Count"] != DBNull.Value ? Convert.ToInt32(dtPendingAssetCount.Rows[0]["Pending_New_Assets_State_Count"]) : 0;
                    dashBoardViewModel.AccountReminder.Pending_Change_Request_State_Count = dtPendingAssetCount.Rows[0]["Pending_Change_Request_State_Count"] != DBNull.Value ? Convert.ToInt32(dtPendingAssetCount.Rows[0]["Pending_Change_Request_State_Count"]) : 0;
                    dashBoardViewModel.AccountReminder.Pending_Valuation_State_Count = dtPendingAssetCount.Rows[0]["Pending_Valuation_State_Count"] != DBNull.Value ? Convert.ToInt32(dtPendingAssetCount.Rows[0]["Pending_Valuation_State_Count"]) : 0;
                }

                dashBoardViewModel.AccountReminder.New_Asset_Pending_State = _context.LookUp.Where(x => x.Field_Name.ToLower() == ConstantStrings.State_Of_Asset.ToLower() && x.Value.ToLower() == State_Of_Asset.New_Asset_Pending.ToLower() && x.FK_Customer == Fk_Customer && !x.Is_Deleted).Select(x => x.PK_LookUp).FirstOrDefault();
                dashBoardViewModel.AccountReminder.Pending_Change_Request_State = _context.LookUp.Where(x => x.Field_Name.ToLower() == ConstantStrings.State_Of_Asset.ToLower() && x.Value.ToLower() == State_Of_Asset.Change_request_Pending.ToLower() && x.FK_Customer == Fk_Customer && !x.Is_Deleted).Select(x => x.PK_LookUp).FirstOrDefault();
                dashBoardViewModel.AccountReminder.Pending_Valuation_State = _context.LookUp.Where(x => x.Field_Name.ToLower() == ConstantStrings.State_Of_Asset.ToLower() && x.Value.ToLower() == State_Of_Asset.Valuation_Pending.ToLower() && x.FK_Customer == Fk_Customer && !x.Is_Deleted).Select(x => x.PK_LookUp).FirstOrDefault();


                dashBoardViewModel.PropertyRisk = _fieldService.GetNumberOfLocationDetails(Fk_Customer);
                return View(dashBoardViewModel);
            }
            else
            {
                return LocalRedirect("~/Identity/Account/Login");
            }
        }
        [HttpGet]
        public async Task<IActionResult> AccountSettings()
        {
            if (HttpContext.Session.GetObjectFromJson<Guid>("CurrentUserId") != Guid.Empty)
            {
                var currentUser = await _userManager.GetUserAsync(HttpContext.User);
                var user = await _userManager.FindByNameAsync(currentUser.UserName);

                AccountSettings model = new AccountSettings();
                List<SelectListItem> List = _customerService.GetCustomerReminderQuestions();
                model.Reminder_Question_List = List;
                model.Email = Convert.ToString(user.Email);
                return View(model);
            }
            else
            {
                return LocalRedirect("~/Identity/Account/Login");
            }
        }
        [HttpPost]
        public async Task<IActionResult> AccountSettings(AccountSettings Account_Setting_Data)
        {
            var currentUser = await _userManager.GetUserAsync(HttpContext.User);
            var user = await _userManager.FindByNameAsync(currentUser.UserName);
            bool Result = false;
            if (user != null)
            {
                if (!string.IsNullOrEmpty(Account_Setting_Data.Email) && !string.IsNullOrEmpty(Account_Setting_Data.Password))
                {
                    user.Email = Account_Setting_Data.Email;
                    user.PasswordHash = _passwordHasher.HashPassword(user, Account_Setting_Data.Password);
                    user.FK_Reminder_Question = Account_Setting_Data.Reminder_Question == null ? user.FK_Reminder_Question : Convert.ToInt32(Account_Setting_Data.Reminder_Question);
                    user.Answer = Account_Setting_Data.Answer == null ? user.Answer : Account_Setting_Data.Answer;
                    var identity = await _userManager.UpdateAsync(user);
                    if (identity.Succeeded)
                        Result = true;
                }
            }
            return Json(Result);
        }
       
        public IActionResult Privacy()
        {
            return View();
        }

        [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
        public IActionResult Error()
        {
            return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
        }

        

    }
}


here is basecontroller which is inherit by homecontroller

using Project.Application.Entities;
using Project.Application.Interfaces;
using Project.Application.ViewModel;
using Project.Common;
using Project.Data.Data;
using Project.Data.Identity;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Project.Controllers
{
    [ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)]
    public class BaseController : Controller
    {
        //private readonly ApplicationSignInManager _signInManager;
        private readonly SignInManager<ApplicationUser> _signInManager;
        private readonly IUserAccessSecurityService _userAccessSecurityService;
        private readonly IDataProtector _protector;
        private readonly IUserSessionLogService _userSessionLogService;
        //public BaseController(ApplicationSignInManager signInManager)
        public BaseController(SignInManager<ApplicationUser> signInManager, IUserAccessSecurityService userAccessSecurityService, IDataProtectionProvider provider, IUserSessionLogService userSessionLogService)
        {
            _signInManager = signInManager;
            _userAccessSecurityService = userAccessSecurityService;
            _protector = provider.CreateProtector("Protector");
            _userSessionLogService = userSessionLogService;
        }
         
        public override void OnActionExecuting(ActionExecutingContext context)
        {
            if (HttpContext.Session.GetObjectFromJson<Guid>("CurrentUserId") == Guid.Empty)
            {
                _signInManager.SignOutAsync();
                bool isAjaxCall = context.HttpContext.Request.Headers["x-requested-with"] == "XMLHttpRequest";
                if(isAjaxCall)
                {
                    context.Result = new UnauthorizedResult();
                }
                else
                {
                    var PK_ID = TempData["UserLogId"];
                    if (Convert.ToInt32(PK_ID) > 0)
                        _userSessionLogService.InsertUpdateSession(0, Convert.ToInt32(PK_ID), "", "", new Guid());
                    context.Result = new RedirectResult("~/Identity/Account/Login");
                }
                return;
            }
            else
            {
                TempData["UserLogId"] = HttpContext.Session.GetObjectFromJson<int>("SessionId");
            }

            if (!HttpContext.Session.GetObjectFromJson<bool>("IsAdmin") && !string.IsNullOrEmpty(context.HttpContext.Request.Query["q"].ToString()))
            {
                //&& context.HttpContext.Request.QueryString.Value!=null && context.HttpContext.Request.QueryString.Value!=""
                var currentUserId = HttpContext.Session.GetObjectFromJson<Guid>("CurrentUserId");
                int CustomerId = HttpContext.Session.GetObjectFromJson<int>("CurrentCustomer");

                var qs = context.HttpContext.Request.Query["q"].ToString();//context.HttpContext.Request.QueryString.Value; //HttpContext.Request.Query["q"];
                var queryString = _protector.Unprotect(qs);
                var parameters = ClsGeneral.GetParams(queryString);
                var pageId = 0;
                var tableName = "";

                if (parameters.TryGetValue("PageId", out string value))
                {
                    pageId = Convert.ToInt32(value);
                }
                if (parameters.TryGetValue("TableName", out value))
                {
                    tableName = Convert.ToString(value);
                }


                List<GroupPagesRights> view_Group_Page_Rights = _userAccessSecurityService.GetGroupPagesRightsByUser(CustomerId, currentUserId);
                if (view_Group_Page_Rights == null || view_Group_Page_Rights.Count == 0 || (!view_Group_Page_Rights.Any(x => x.FK_Page == pageId)))
                {
                    context.HttpContext.Response.Redirect(Url.Action("Unauthorised", "Home"));
                }
               
            }
            
            base.OnActionExecuting(context);
        }
    }
}

Please help

Thanks

JahiMaf
  • 3
  • 6
  • 1
    Remove `OnActionExecuting` from the base controller and `ResponseCache` from everywhere. Establish a baseline that works properly before introducing caching & other things that build on top of auth – abdusco Aug 02 '21 at 07:47
  • @abdusco Yes your guidance help me , Now I am doing step by step changes for my requirement. Thanks A lot!! – JahiMaf Aug 02 '21 at 08:28
  • Hey @abdusco remember you told me that "You can set HttpContext.User = oktaResult.Principal but that wouldn't work because user is redirected, and is dispatched to Home.Index in the next request " . it is happening my Httpcontext.user lost his value on home controller redirect, but i need it to perfome some operation like my view render based on user.isauthenticated and all . – JahiMaf Aug 02 '21 at 09:33
  • Hey @abdusco i use this code _signInManager.SignInAsync(user, new Microsoft.AspNetCore.Authentication.AuthenticationProperties { ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes(Convert.ToDouble(timeout)) }); after that it is working now, is that correct way to do ? after okta login i manually make this call same like Microsoft identity . – JahiMaf Aug 03 '21 at 05:42

1 Answers1

2

If you don't have an [Authorize] on an action or on the class, or have a fallback auth policy that authenticates the user, the user will not be authenticated.

We can put an [Authorize] on SignIn, but that would defeat the purpose.

You have one other option, manually authenticate and check if succeeds:

[HttpGet]
public async Task<ActionResult> SignIn()
{
    var oktaResult = await HttpContext.AuthenticateAsync(OktaDefaults.MvcAuthenticationScheme);
    if (oktaResult.Succeeded)
    {
        return RedirectToAction("Index", "Home");
    }
    
    return Challenge(OktaDefaults.MvcAuthenticationScheme);
}
abdusco
  • 9,700
  • 2
  • 27
  • 44
  • Thank you so much @abdusco it worked and it is not in infinite loop now but when redirectToAction calls , Index method is not getting call i have added debugger there and method is define in home Controller. – JahiMaf Jul 30 '21 at 08:01
  • Does the `Index` method have `[Authorize]` attribute? Because there's the assumption that whoever's visiting index must be authenticated – abdusco Jul 30 '21 at 08:09
  • When i use simple login with database , on login but click it redirect me to home controller's index after microsoft identity singinaync call. – JahiMaf Jul 30 '21 at 08:10
  • but i have added button call sign in with okta. previously it was not even authenticating , thanks to you after your help it authenticate , but now not redirecting to home controller's index – JahiMaf Jul 30 '21 at 08:11
  • Are you getting an error? Check the logs (Set `Microsoft.AspNetCore` log level to `Debug`) – abdusco Jul 30 '21 at 08:17
  • Not Getting Any Error. – JahiMaf Jul 30 '21 at 08:33
  • HttpContext.User.Identity.IsAuthenticated this is always false, even if HttpContext.AuthenticateAsync(OktaDefaults.MvcAuthenticationScheme) this return true and okta result is succeed. – JahiMaf Jul 30 '21 at 08:50
  • `AuthenticateAsync` does not replace `HttpContext.User`, that's why `IsAuthenticated` is false. You need to put `[Authorize]` attribute on the action you want to access authenticated user. You can set `HttpContext.User = oktaResult.Principal` but that wouldn't work because user is redirected, and is dispatched to `Home.Index` in the **next request** . – abdusco Jul 30 '21 at 08:52
  • I did that but not working , in my project i have microsoft identity already , do you think okta identity provider and microsoft identity causing each other problem ? – JahiMaf Jul 30 '21 at 09:35
  • see this: https://stackoverflow.com/questions/68580179/add-additional-authentication-provider-but-keep-current-session-data/68581461 – abdusco Jul 30 '21 at 09:44
  • Yes i have check that, but that is different case and as per that my startup.cs is looks fine can you please review it ? – JahiMaf Jul 30 '21 at 10:07
  • Was Microsoft Identity working fine before adding Okta? – abdusco Jul 30 '21 at 10:44
  • Yes working fine now also, if i add , username password and hit login button , i have added okta and give another button called "login with okta" in that it is not working – JahiMaf Jul 30 '21 at 10:51
  • HttpContext.AuthenticateAsync(OktaDefaults.MvcAuthenticationScheme) this return true but it remain on login page – JahiMaf Jul 30 '21 at 10:52
  • Is the Okta scheme actually creating the authentication cookie automatically? I'm guessing it must if that's set as the default scheme but it's worth checking that the cookie is being set in the callback and that it contains the claims you need. If using ASP.Net Identity you may need to add a step in between so you can have fine control over how the external account is linked up. Example here using SigninManager: https://www.blinkingcaret.com/2017/05/03/external-login-providers-in-asp-net-core/ – mackie Jul 30 '21 at 12:19
  • @mackie default signin auth scheme is `CookieAuthenticationDefaults.AuthenticationScheme` meaning Okta cookies is written to it. – abdusco Jul 30 '21 at 12:29
  • @abdusco please help me , I am stuck in this from many days. – JahiMaf Aug 02 '21 at 04:53
  • I think it is not redirect me to home index becaue even after HttpContext.AuthenticateAsync(OktaDefaults.MvcAuthenticationScheme) this call it think that user is not authenticated. because home controller will require an authorization. – JahiMaf Aug 02 '21 at 05:17
  • @JahiMaf add your home controller to the post – abdusco Aug 02 '21 at 06:17
  • @abdusco I have added home controller and basecontroller which is inherit by home controller in post – JahiMaf Aug 02 '21 at 07:10