I tried to rename the tables with the following code:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<IdentityUser>().ToTable("Users").Property(p => p.Id).HasColumnName("UserId");
modelBuilder.Entity<ApplicationUser>().ToTable("Users").Property(p => p.Id).HasColumnName("UserId");
modelBuilder.Entity<IdentityRole>().ToTable("Roles");
modelBuilder.Entity<IdentityUserRole>().ToTable("UserRoles");
modelBuilder.Entity<IdentityUserLogin>().ToTable("UserLogins");
modelBuilder.Entity<IdentityUserClaim>().ToTable("UserClaims");
}
It worked. The table names have been renamed properly, and data seems to be getting inserted in the proper places.
However, The User.IsInRole("rolestring") method doesn't work. It returns false all the time.
If I remove the above code, everything works fine.
What am I missing?
Update:
On your Startup class, when configure the OAuthAuthorizationServerOptions, on Provider property you should have a custom class that inherits from OAuthAuthorizationServerProvider. In the example below, the CustomAuthorizationServerProvider class:
OAuthAuthorizationServerOptions oAuthServerOptions = new OAuthAuthorizationServerOptions
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(30),
Provider = new CustomAuthorizationServerProvider()
};
And this is code of CustomAuthorizationServerProvider, where you have to override GrantResourceOwnerCredentials:
public class CustomAuthorizationServerProvider : OAuthAuthorizationServerProvider
{
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
...
context.Validated();
return Task.FromResult<object>(null);
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var allowedOrigin = context.OwinContext.Get<string>("as:clientAllowedOrigin");
if (allowedOrigin == null) allowedOrigin = "*";
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { allowedOrigin });
IdentityUser user;
using (AuthRepository repository = new AuthRepository())
{
user = await repository.FindUser(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect");
return;
}
}
UserManager<IdentityUser> userManager = new UserManager<IdentityUser>(new UserStore<IdentityUser>(new AuthDBContext()));
ClaimsIdentity identity = await userManager.CreateIdentityAsync(user, context.Options.AuthenticationType);
AuthenticationProperties properties = new AuthenticationProperties(new Dictionary<string, string>
{
{
"as:client_id", context.ClientId ?? string.Empty
},
{
"userName", context.UserName
},
{
"roles",String.Join(",", (IEnumerable<IdentityUserRole>) user.Roles.ToArray())
}
});
AuthenticationTicket ticket = new AuthenticationTicket(identity, properties);
context.Validated(ticket);
}
public override Task TokenEndpoint(OAuthTokenEndpointContext context)
{
foreach (KeyValuePair<string, string> property in context.Properties.Dictionary)
{
context.AdditionalResponseParameters.Add(property.Key, property.Value);
}
return Task.FromResult<object>(null);
}
}
Note: AuthDBContext is the same class you are declaring your OnModelCreating method.
So, reviewing the code above, you'll check that user roles are inserted in AuthenticationProperties dictionary
{
"roles",String.Join(",", (IEnumerable<IdentityUserRole>) user.Roles.ToArray())
}
and then they are inserted in the ticket with the ClaimsIdentity object of the current user.
AuthenticationTicket ticket = new AuthenticationTicket(identity, properties);
Once you've fixed this, you only have to add the [Authorize] attribute in your actions and/or controllers like this:
[Authorize(Roles = "Admin")]
Or check in your controller actions the equivalent:
ActionContext.RequestContext.Principal.IsInRole("Admin")
Related
I have created a Managed Identity (User Assigned) using Azure portal.
I attached that MSI with Azure App Service
Added appropriate permissions for the MSI at Azure SQL (Database)
In this implementation I am using Microsoft.EntityFrameworkCore version 2.2.6
I have the following code :
IDBAuthTokenService.cs
public interface IDBAuthTokenService
{
Task<string> GetTokenAsync();
}
AzureSqlAuthTokenService.cs
public class AzureSqlAuthTokenService : IDBAuthTokenService
{
public readonly IConfiguration _configuration;
public AzureSqlAuthTokenService(IConfiguration configuration)
{
_configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
}
public async Task<string> GetTokenAsync()
{
var credential = new DefaultAzureCredential(new DefaultAzureCredentialOptions{ManagedIdentityClientId = _configuration[C.AppKeys.UserAssignedClientId]});
var tokenRequestContext = new TokenRequestContext(new[]{_configuration[C.AppKeys.AzureSQLResourceId]});
var token = await credential.GetTokenAsync(tokenRequestContext, default);
return token.Token;
}
}
TestDbContext.cs:
public partial class TestDbContext : DbContext
{
public TestDbContext()
{
}
public TestDbContext(IDBAuthTokenService tokenService, DbContextOptions<TestDbContext> options) : base(options)
{
var connection = this.Database.GetDbConnection() as SqlConnection;
connection.AccessToken = tokenService.GetTokenAsync().Result;
}
public virtual DbSet<HealthCheckData> HealthCheckData { get; set; }
}
TestReportServiceProvider.cs
public class TestReportServiceProvider : IReportService
{
private readonly TestDbContext _objDBContext;
public TestReportServiceProvider(TestDbContext objDBContext)
{
_objDBContext = objDBContext;
}
public dynamic GetDataDetails(ReportDTO filters)
{
var response = new TestReponseExcelDto();
var ds = new DataSet();
using (var connection = new SqlConnection(_objDBContext.Database.GetDbConnection().ConnectionString))
{
connection.Open();
using (var command = new SqlCommand())
{
command.Connection = connection;
command.CommandType = CommandType.StoredProcedure;
command.CommandText = "[CR].[LoadProcedureDetailPopup]";
using (var sda = new SqlDataAdapter())
{
sda.SelectCommand = command;
sda.Fill(ds);
}
}
connection.Close();
}
if (ds.Tables.Count > 0)
{
response.Data = GetData(ds.Tables[0]);
response.TotalEngagements = response.Data.Select(d => d.TestReviewId).Distinct().Count();
}
return response;
}
}
In the above code while debugging I found error: Login failed for user ''. just after the control passes the code snippet connection.Open();. Even though the AccessToken was setup at the constructor within the TestDbContext , in this case I noticed that it is assigned with null value.
I added the below code before opening the connection and it started working fine as expected:
connection.AccessToken = ((SqlConnection)_objDBContext.Database.GetDbConnection()).AccessToken;
Even though my fix is solving the issue, I wanted to know whether it is correct way of doing it or are there better ways to manage it.
Can anyone help me to resolve this issue?
I'm currently trying to get Okta to work with our ASP.Net MVC 4.7 based application. what i observe okta login get successfully but Unfortunatly After the authentication (saml response accepted) challenge, ExternalLoginCallback is called then checks if Okta info is present to use for own authentication but it always return null refer ExternalLoginCallback method. or https://github.com/bvillanueva-mdsol/OktaSaml2OwinSample/issues/1 as code base and also raised issue in git hub for respective owner.
<add key="ApplicationBaseUri" value="https://localhost:2687" />
<add key="IdentityProviderIssuer" value="http://www.okta.com/exk3js0t73vBlN4Vq5d7" />
<add key="IdentityProviderSsoUri" value="https://dev-00349616.okta.com/app/dev-00349616_httpslocalhost2687signinsaml_1/exk3js0t73vBlN4Vq5d7/sso/saml" />
public void Configuration(IAppBuilder app)
{
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
AuthenticationMode = AuthenticationMode.Active
});
app.UseSaml2Authentication(CreateSaml2Options());
}
private static Saml2AuthenticationOptions CreateSaml2Options()
{
var applicationBaseUri = new Uri(ConfigurationManager.AppSettings["ApplicationBaseUri"]);
var saml2BaseUri = new Uri(applicationBaseUri, "saml2");
var identityProviderIssuer = ConfigurationManager.AppSettings["IdentityProviderIssuer"];
var identityProviderSsoUri = new Uri(ConfigurationManager.AppSettings["IdentityProviderSsoUri"]);
var Saml2Options = new Saml2AuthenticationOptions(false)
{
SPOptions = new SPOptions
{
EntityId = new EntityId(saml2BaseUri.AbsoluteUri),
ReturnUrl = applicationBaseUri
}
};
var identityProvider = new IdentityProvider(new EntityId(identityProviderIssuer), Saml2Options.SPOptions)
{
AllowUnsolicitedAuthnResponse = true,
Binding = Saml2BindingType.HttpRedirect,
SingleSignOnServiceUrl = identityProviderSsoUri
};
identityProvider.SigningKeys.AddConfiguredKey(
new X509Certificate2(
HostingEnvironment.MapPath(
"~/App_Data/okta.cert")));
Saml2Options.IdentityProviders.Add(identityProvider);
return Saml2Options;
}
AccountController.cs file
[AllowAnonymous]
public ActionResult Login(string returnUrl)
{
//ControllerContext.HttpContext.Session.RemoveAll();
return new Saml2ChallengeResult(Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl }));
}
[AllowAnonymous]
public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{
var loginInfo = await HttpContext.GetOwinContext().Authentication.GetExternalLoginInfoAsync();
if (loginInfo == null) // always return null
{
return RedirectToAction("LoginError");
}
var identity = new ClaimsIdentity(loginInfo.ExternalIdentity.Claims,
DefaultAuthenticationTypes.ApplicationCookie);
var authProps = new AuthenticationProperties
{
IsPersistent = true,
ExpiresUtc = DateTime.UtcNow.AddMinutes(1)
};
HttpContext.GetOwinContext().Authentication.SignIn(authProps, identity);
return RedirectToLocal(returnUrl);
}
[AllowAnonymous]
public ActionResult LoginError()
{
return Content("Error Logging in!");
}
private IAuthenticationManager AuthenticationManager =>
HttpContext.GetOwinContext().Authentication;
private ActionResult RedirectToLocal(string returnUrl)
{
if (Url.IsLocalUrl(returnUrl))
{
return Redirect(returnUrl);
}
return RedirectToAction("Index", "Home");
}
internal class Saml2ChallengeResult : HttpUnauthorizedResult
{
public string RedirectUri { get; set; }
public Saml2ChallengeResult(string redirectUri)
{
RedirectUri = redirectUri;
}
public override void ExecuteResult(ControllerContext context)
{
context.RequestContext.HttpContext.Response.SuppressFormsAuthenticationRedirect = true;
var properties = new AuthenticationProperties { RedirectUri = RedirectUri };
context.HttpContext.GetOwinContext().Authentication.Challenge(properties, "Saml2");
}
}
}
Solution is more related to correct steps which I missed.
code is absolutely fine and it will work
In order to run the application we have roslyn folder in bin folder and by mistake I copied roslyn folder from RUUNING https://localhost:44376 application. We should not copy and paste roslyn folder from running application to https://localhost:2687.
Clue :
surprisingly IIS shows 2 application running even https://localhost:44376 visual studio application was closed.
and now I am getting login info details from okta
I'm starting a new project using MVC 5, Identity 2.x, Unity, and Dapper. I'm using the standard EF functionality for Identity but using Dapper for the rest of the DB access. I'm using a Repository Pattern for all my (non-Identity) DB calls.
I'm fairly new to Unity and Dapper but keep gettin a "Object reference not set to an instance of an object." error whenever I make a call to the DB interface in the Account Controller line from below:
var result = _companyaccountrepository.AddToCompanyUsers(model);
Can anyone point out what I'm doing wrong? Thanks in advance.
Account Controller
private ICompanyAccountRepository _companyaccountrepository { get; set; }
public ICompanyAccountRepository companyaccountrepository
{
get { return _companyaccountrepository ?? (_companyaccountrepository = new CompanyAccountRepository()); }
}
private ApplicationUserManager _userManager;
public ApplicationUserManager UserManager
{
get
{
return _userManager ?? HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
}
private set
{
_userManager = value;
}
}
private ApplicationSignInManager _signInManager;
public ApplicationSignInManager SignInManager
{
get
{
return _signInManager ?? HttpContext.GetOwinContext().Get<ApplicationSignInManager>();
}
private set { _signInManager = value; }
}
public AccountController()
{
}
public AccountController(ApplicationUserManager userManager, ApplicationSignInManager signInManager, ICompanyAccountRepository companyaccountrepository)
{
UserManager = userManager;
SignInManager = signInManager;
_companyaccountrepository = companyaccountrepository;
}
...
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> SignUp(RegisterUserAndCompanyViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
user.FirstName = model.FirstName;
user.LastName = model.LastName;
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
var result = _companyaccountrepository.AddToCompanyUsers(model); //*** THIS IS WHERE THE PROBLEM OCCURS ****
return RedirectToAction("Confirmation");
}
AddErrors(result);
}
// If we got this far, something failed, redisplay form
return View(model);
}
Interface/Dapper SQL (dummy code to make it simple)
public interface ICompanyAccountRepository
{
CompanyUser AddToCompanyUsers(RegisterUserAndCompanyViewModel user);
}
public class CompanyAccountRepository : ICompanyAccountRepository
{
private string dbconn = ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString;
public bool AddToCompanyUsers(RegisterUserAndCompanyViewModel user);
{
using (SqlConnection cn = new SqlConnection(dbconn))
{
cn.Open();
cn.Insert(new CompanyUser() { CompanyId = user.companyid, UserId = user.id });
cn.Close();
}
return true;
}
}
Unity.Config
public static void RegisterTypes(IUnityContainer container)
{
// NOTE: To load from web.config uncomment the line below. Make sure to add a Microsoft.Practices.Unity.Configuration to the using statements.
// container.LoadConfiguration();
// TODO: Register your types here
//12-1-16 Need this for Identity
container.RegisterType<ApplicationDbContext>();
container.RegisterType<ApplicationSignInManager>();
container.RegisterType<ApplicationUserManager>();
container.RegisterType<EmailService>();
container.RegisterType<IAuthenticationManager>(
new InjectionFactory(c => HttpContext.Current.GetOwinContext().Authentication));
container.RegisterType<IUserStore<ApplicationUser>, UserStore<ApplicationUser>>(
new InjectionConstructor(typeof(ApplicationDbContext)));
container.RegisterType<AccountController>(
new InjectionConstructor(typeof(ApplicationUserManager), typeof(ApplicationSignInManager), typeof(ICompanyAccountRepository)));
container.RegisterType<AccountController>(
new InjectionConstructor());
//Identity / Unity stuff below to fix No IUserToken Issue - http://stackoverflow.com/questions/24731426/register-iauthenticationmanager-with-unity
//container.RegisterType<DbContext, ApplicationDbContext>(
// new HierarchicalLifetimeManager());
container.RegisterType<UserManager<ApplicationUser>>(
new HierarchicalLifetimeManager());
container.RegisterType<IUserStore<ApplicationUser>, UserStore<ApplicationUser>>(
new HierarchicalLifetimeManager());
container.RegisterType<ICompanyAccountRepository, CompanyAccountRepository>();
}
Thanks again for any suggestions.
NOTE: If I add instantiate the repository just before the AddToCompanyUsers call (below), it works fine. However, this breaks Unity/IOC
_companyaccountrepository= new CompanyAccountRepository();
var result = _companyaccountrepository.AddToCompanyUsers(model);
You can try it like this:
(this should fix your repository error. As for your userManager and signInManager, I believe you can improve how they are configured as well, but that will take to take a look on your startup.auth and your ApplicationDbContext and with all the Identity configuration)
Account Controller
private readonly ICompanyAccountRepository _companyaccountrepository;// { get; set; } -- remove the getter and setter here
//remove this
// public ICompanyAccountRepository companyaccountrepository
// {
// get { return _companyaccountrepository ?? (_companyaccountrepository = new CompanyAccountRepository()); }
// }
private ApplicationUserManager _userManager;
public ApplicationUserManager UserManager
{
get
{
return _userManager ?? HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
}
private set
{
_userManager = value;
}
}
private ApplicationSignInManager _signInManager;
public ApplicationSignInManager SignInManager
{
get
{
return _signInManager ?? HttpContext.GetOwinContext().Get<ApplicationSignInManager>();
}
private set { _signInManager = value; }
}
//I think you can remove the parameterless constructor as well
//public AccountController()
//{
//
//}
public AccountController(ApplicationUserManager userManager, ApplicationSignInManager signInManager, ICompanyAccountRepository companyaccountrepository)
{
UserManager = userManager;
SignInManager = signInManager;
_companyaccountrepository = companyaccountrepository;
}
...
EDIT
Change your constructor to:
public AccountController(ICompanyAccountRepository companyaccountrepository)
{
_companyaccountrepository = companyaccountrepository;
}
In my multitenant application user permissions (read Roles if you are more comfortable with that) are set per tenant so we are adding claims to each user with value TenantName:Permission.
We are using policy based authorization with custom code using the following code
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class PermissionAuthorizeAttribute : AuthorizeAttribute
{
public Permission[] AcceptedPermissions { get; set; }
public PermissionAuthorizeAttribute()
{
}
public PermissionAuthorizeAttribute(params Permission[] acceptedPermissions)
{
AcceptedPermissions = acceptedPermissions;
Policy = "RequirePermission";
}
}
public enum Permission
{
Login = 1,
AddUser = 2,
EditOtherUser = 3,
EditBaseData = 6,
EditSettings = 7,
}
With the above code we decorate Controller actions
[PermissionAuthorize(Permission.EditSettings)]
public IActionResult Index()
In startup.cs we have
services.AddAuthorization(options =>
{
options.AddPolicy("RequirePermission", policy => policy.Requirements.Add(new PermissionRequirement()));
});
The AuthorizationHandler would in this case need access to the PermissionAuthorizeAttribute so we can check what permissions were specified on the action. At the moment we can get the attribute with the below code, but I think there must be an easier way, since there is a lot of casting an iterating there.
public class PermissionRequirement : AuthorizationHandler<PermissionRequirement>, IAuthorizationRequirement
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement)
{
var filters = ((FilterContext)context.Resource).Filters;
PermissionAuthorizeAttribute permissionRequirement = null;
foreach (var filter in filters)
{
var authorizeFilter = filter as AuthorizeFilter;
if (authorizeFilter == null || authorizeFilter.AuthorizeData == null)
continue;
foreach (var item in authorizeFilter.AuthorizeData)
{
permissionRequirement = item as PermissionAuthorizeAttribute;
if (permissionRequirement != null)
break;
}
if (permissionRequirement != null)
break;
}
//TODO Check that the user has the required claims
context.Succeed(requirement);
return Task.CompletedTask;
}
}
All the examples I have found are like this, where some hardwired policy is specified in startup.cs.
services.AddAuthorization(options =>
{
options.AddPolicy("Over21",
policy => policy.Requirements.Add(new MinimumAgeRequirement(21)));
}
Here you decorate you controller or action with
[Authorize(Policy="Over21")]
public class AlcoholPurchaseRequirementsController : Controller
I think the above example would be better if you could specify the age in the controller/action, like this
[Authorize(Policy="OverAge", Age=21)]
public class AlcoholPurchaseRequirementsController : Controller
Now you need to add a different policy for every minimum age.
Any ideas on how to make this efficient?
Although i would use Resource Based Authorization as i commented, there is a way to achieve your goal:
First create a custom attribute:
public class AgeAuthorizeAttribute : Attribute
{
public int Age{ get; set; }
public AgeAuthorizeAttribute(int age)
{
Age = age;
}
}
Then write a filter provider:
public class CustomFilterProvider : IFilterProvider
{
public int Order
{
get
{
return 0;
}
}
public void OnProvidersExecuted(FilterProviderContext context)
{
}
public void OnProvidersExecuting(FilterProviderContext context)
{
var ctrl = context.ActionContext.ActionDescriptor as ControllerActionDescriptor;
var ageAttr = ctrl.MethodInfo.GetCustomAttribute<AgeAuthorizeAttribute>();
if (ageAttr == null)
{
ageAttr = ctrl.ControllerTypeInfo.GetCustomAttribute<AgeAuthorizeAttribute>();
}
if (ageAttr != null)
{
var policy = new AuthorizationPolicyBuilder()
.AddRequirements(new MinimumAgeRequirement(ageAttr.Age))
.Build();
var filter = new AuthorizeFilter(policy);
context.Results.Add(new FilterItem(new FilterDescriptor(filter, FilterScope.Action), filter));
}
}
}
Finally register filter provider:
services.TryAddEnumerable(ServiceDescriptor.Singleton<IFilterProvider, CustomFilterProvider>());
And use it
[AgeAuthorize(21)]
public IActionResult SomeAction()
... or
[AgeAuthorize(21)]
public class AlcoholPurchaseRequirementsController : Controller
ps: not tested
I have read the documentation and have successfully implemented a custom authentication layer like below:
public class SmartLaneAuthentication : CredentialsAuthProvider
{
private readonly SmartDBEntities _dbEntities;
public SmartLaneAuthentication(SmartDBEntities dbEntities)
{
_dbEntities = dbEntities;
}
public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
{
var user = _dbEntities.Users.FirstOrDefault(x => !((bool)x.ActiveDirectoryAccount) && x.UserName == userName);
if (user == null) return false;
// Do my encryption, code taken out for simplicity
return password == user.Password;
}
public override void OnAuthenticated(IServiceBase authService, IAuthSession session, IOAuthTokens tokens, Dictionary<string, string> authInfo)
{
// user should never be null as it's already been authenticated
var user = _dbEntities.Users.First(x => x.UserName == session.UserAuthName);
var customerCount = _dbEntities.Customers.Count();
session.UserName = user.UserName;
session.DisplayName = user.DisplayName;
session.CustomerCount = customerCount; // this isn't accessible?
authService.SaveSession(session, SessionExpiry);
}
}
I then register it in AppHost:
Plugins.Add(new AuthFeature(() => new SmartLaneUserSession(),
new IAuthProvider[]
{
new SmartLaneAuthentication(connection)
})
{
HtmlRedirect = null
});
Plugins.Add(new SessionFeature());
Notice I'm using a SmartLaneUserSession like below, where I have added a Custom Property called CustomerCount:
public class SmartLaneUserSession : AuthUserSession
{
public int CustomerCount { get; set; }
}
When I try and access this property to set it in the OnAuthenticated method of my SmartLaneAuthentication class, it isn't accessible. How would I access and set this property when the user is logged in?
In the OnAuthenticated method you will need to cast the session (of type IAuthSession) into your session object type, such as:
...
var customerCount = _dbEntities.Customers.Count();
var smartLaneUserSession = session as SmartLaneUserSession;
if(smartLaneUserSession != null)
{
smartLaneUserSession.UserName = user.UserName;
smartLaneUserSession.DisplayName = user.DisplayName;
smartLaneUserSession.CustomerCount = customerCount; // Now accessible
// Save the smartLaneUserSession object
authService.SaveSession(smartLaneUserSession, SessionExpiry);
}
In your service you can access the session using the SessionAs<T> method. So in your case you can use:
public class MyService : Service
{
public int Get(TestRequest request)
{
var session = SessionAs<SmartLaneUserSession>();
return session.CustomerCount;
}
}