Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Capturing login event so I can cache other user information

I've built a web application. When I built it I ticked 'Organizational Accounts'

It works well - I log in with my Office 365 account and User.Identity.Name contains the email address

This application is a replacement front end for an older ASP Classic app. The App has an existing security table that I need to use.

I want to use the email address to look up a record in this table to get

  • The internal database key for the user (so I can use it in database calls)

  • The security level (authorisation) for the user

I want to look this up as soon as I am authenticated and save these two values to Session to refer to later

I have an existing method that does all of this lookup and caching. I actually got it working by calling it from the _LoginPartial.cshtml view but clearly it is incorrect to be triggering this kind of thing from a view

Here's the code to look up and cache user info. For now this is in AccountController.cs but it doesn't have to be

private Boolean GetAdditionalUserInfo()
{
    // if authentication info is saved, don't go find it
    if (Session["UID"] != null) return true;

    // get the db employee id from the database and save it to the session
    var r = (
            from e in db.Employees
            where e.Email == User.Identity.Name
            select new
            {
                e.Emp_ID,
                e.Group_ID
            }
            ).SingleOrDefault();

    if ((r == null) || (r.Group_ID == (int)Role.Inactive))
    {
        // couldn't find record or inactive
        return false;
    }

    // Update last login datetime
    Employee ell = db.Employees.Find(r.Emp_ID);
    ell.LastLogin = DateTime.Now;
    db.SaveChangesAsync();

    // Save user details to the session
    Session["UID"] = r.Emp_ID;
    // TBD: Investigate "CLAIMS" - this should probably be a claim
    Session["Role"] = r.Group_ID;

    return true;

}

I think the reference to User.Identity.Name triggers the login process so I could either just try and call this at startup (I don't know the correct way to do this), or I think the proper thing to do is to call this using the OnAuthentication method, and to link it up I should pass the name of my function to the OnAuthenticated property. Here's two links to the method and property:

https://msdn.microsoft.com/en-us/library/system.web.mvc.controller.onauthentication(v=vs.118).aspx

https://msdn.microsoft.com/en-us/library/microsoft.owin.security.microsoftaccount.microsoftaccountauthenticationprovider.onauthenticated(v=vs.113).aspx

But I have to say that OO programming is not my thing and I can't work out from these pages how to use them or which class to put them into.

This page implies it needs to go into Startup.Auth.cs but my Startup.Auth.cs looks nothing like that one. Here is most of my Startup.Auth.cs which was mostly autogenerated when I ticked 'organisational' at the start. (On a side note, app.UseKentorOwinCookieSaver(); is my next challenge because apparently organisational login doesn't work with Session can you believe it!!!)

Can anyone help me add required code to call GetAdditionalUserInfo()? after login? Or alternatively confirm that I can just call this at startup, and suggest the correct way to do it.

public partial class Startup
{
    private static string clientId = ConfigurationManager.AppSettings["ida:ClientId"];
    private static string aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"];
    private static string tenantId = ConfigurationManager.AppSettings["ida:TenantId"];
    private static string postLogoutRedirectUri = ConfigurationManager.AppSettings["ida:PostLogoutRedirectUri"];
//private static string authority = aadInstance + tenantId;
// to make this multi tenant, use common endpoint, not the tenant specific endpoint
private static string authority = aadInstance + "common";

public void ConfigureAuth(IAppBuilder app)
{
    app.SetDefaultSignInAsAuthenticationType(
        CookieAuthenticationDefaults.AuthenticationType);

    // https://stackoverflow.com/questions/20737578/asp-net-sessionid-owin-cookies-do-not-send-to-browser
    app.UseKentorOwinCookieSaver();

    app.UseCookieAuthentication(
        new CookieAuthenticationOptions());

    app.UseOpenIdConnectAuthentication(
        new OpenIdConnectAuthenticationOptions
        {
            ClientId = clientId,
            Authority = authority,
            PostLogoutRedirectUri = postLogoutRedirectUri,
            TokenValidationParameters = new TokenValidationParameters
            {
                // If you don't add this, you get IDX10205
                // from here http://charliedigital.com/2015/03/14/adding-support-for-azure-ad-login-o365-to-mvc-apps/
                ValidateIssuer = false                        
            },
            Notifications = new OpenIdConnectAuthenticationNotifications
            {
                RedirectToIdentityProvider = ctx =>
                {
                    bool isAjaxRequest = (ctx.Request.Headers != null && ctx.Request.Headers["X-Requested-With"] == "XMLHttpRequest");

                    if (isAjaxRequest)
                    {
                        ctx.Response.Headers.Remove("Set-Cookie");
                        ctx.State = NotificationResultState.HandledResponse;
                    }

                    return System.Threading.Tasks.Task.FromResult(0);
                }
            }
        });
   }
}
like image 827
Nick.McDermaid Avatar asked Apr 11 '17 10:04

Nick.McDermaid


1 Answers

In article The OWIN OpenID Connect Middleware you can find detailed explanation how you could use Notifications. In you case you should subscribe to SecurityTokenValidated:

RedirectToIdentityProvider = ctx => {...},
SecurityTokenValidated = (context) =>
{
    string userID = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value;

    // Here you can retrieve information from Database. Let's say you get r.Group_ID.

    var role = r.Group_ID;

    // You can now add it to Identity and no need to use session.

    Claim roleClaim = new Claim(
        "http://nomatterwhatyouput/role",
        role,
        ClaimValueTypes.[RoleType],
        "LocalAuthority");
    context.AuthenticationTicket.Identity.AddClaim(roleClaim);

    // Do same for all values you have. Remember to set unique claim URL for each value.

    return Task.CompletedTask;
},

And then you can retrieve those values in you actions as mentioned in that article:

public ActionResult Index()
{
    var role = ClaimsPrincipal.Current.FindFirst("http://nomatterwhatyouput/role");
    return View();
}
like image 52
Andrii Litvinov Avatar answered Oct 20 '22 05:10

Andrii Litvinov