How to Audit Login activity in IdentityServer3

In some use cases there is a security requirement for Auditing of User Login Activity, which is not covered by IdentityServer3.

As we used the ASP.NET Identity as User Store, it also possible to implement some sort of Auditing as a custom SignInManager.cs in ASP.NET Identity, but that would not cover all Login Features that are supported by IdentityServer3, which us great Framework by the way and I had much joy of using it and I look forward of using the latest port of IdentityServer on ASP.NET Core.

Searching for a best way to implement this, I found out two interesting Features in Framework:

  • Logging (more for Development-time logging, that is produces a quite extensive output and not that great for Auditing)
  • Events (much better for production-time environments, firing four types of Events and is configurable. More on Events in IdentityServer3 here)

 

First of all, we will extend our IdentityDbContext that we used for ASP.NET Identity User Storage with another Class called UserAudit.cs with all the columns I want to store:

[Table("UserAuditEvents")]

public class UserAudit

{

[Key]

public int Id { get; private set; }

[Required]

public string UserId { get; private set; }

[Required]

public DateTimeOffset Timestamp { get; private set; } = DateTime.UtcNow;

[Required]

public EventTypes AuditEvent { get; set; }

public string IpAddress { get; private set; }

public static UserAudit CreateAuditEvent(string userId, EventTypes auditEventType, string ipAddress)

{

return new UserAudit { UserId = userId, AuditEvent = auditEventType, IpAddress = ipAddress };

}

}

For EventTypes Enum you will need to add using of Thinktecture.IdentityServer.Core.Events namespace. It contains Integer values for following Events: Success = 1, Failure = 2, Information = 3 and Error = 4.

 

Now that we have a class which represents the database table, we should add it to the entity framework IdentityDbContext:

public class LoginDbContext : IdentityDbContext<User, Role, string, IdentityUserLogin, IdentityUserRole, IdentityUserClaim>, ILoginDbContext

{

public virtual DbSet<UserAudit> UserAuditEvents { get; set; }

}

At this point, we have a new database table defined which needs to be physically created. We will do this by creating a migration and applying it to the database or if you have AutomaticMigrationsEnabled = true then you just have to update your database by running the "Update-Database" in Package Manager Console of your Project.

Now we have to implement our AuditEventService.cs as an Implementation of IEventService.cs.

public class AuditEventService : IEventService

{

private readonly LoginDbContext _db;

public AuditEventService(LoginDbContext dbContext)

{

_db = dbContext;

}

public void Raise<T>(Event<T> evt)

{

var localLoginDetails = evt.Details as LocalLoginDetails;

if (localLoginDetails != null)

{

EnBWUserAudit newAuditRecord = EnBWUserAudit.CreateAuditEvent(

localLoginDetails.LoginUserName,

evt.EventType,

HttpContext.Current.Request.UserHostAddress

);

_db.UserAuditEvents.Add(newAuditRecord);

_db.SaveChangesAsync();

}

}

}

 

As you can see, Raise<T> function will be called when any Event is fired and because we are implementing Login Auditing, I am only interested in Events.Details of type LocalLoginDetails as they contain all relevant information about the Login that we want to audit. Also, we are Injecting the DbContext into the Construct of the Service because we need it for storing our audit records. This Injection needs to be registered separated from our IEventService in order to work. Code for this in a minute.

 

So the final part is here! Now we need to change our Startup.cs Class and add few lines to our IdentityServerServiceFactory.

var factory = new IdentityServerServiceFactory();

First we need to register our AuditEventService and then the DbContext to the IdentityServerServiceFactory in order for Dependency Injection to work:

factory.EventService = new Registration<IEventService, AuditEventService>();

factory.Register(new Registration<LoginDbContext>(resolver => new LoginDbContext()));

Now, it should all be working if there was not one "small" thing: IEventService configurations stored in EventOptions Class is by default turned off for all types of events. We need to add EventOptions to our IdentityServerOptions in order to get Events fired at all.

My IdentityServerOptions looked at the end like this:

var eventOptions = new EventsOptions()

{

RaiseSuccessEvents = true,

RaiseFailureEvents = true

};

 

Finally, we can try to Login to our Application. If all went good, you should be able to see something like this in you Database Table "UserAuditEvents":

 

Summing Up

This blog post hopefully demonstrates the initial steps that we can follow to quite easily implement the IEventService in IdentityServer3 and extend it with nice auditing feature. I expect to be refactoring and extending this code further as my requirements determine.


Posted Sep 23 2016, 10:41 AM by Armin Kalajdzija

Add a Comment

(required)  
(optional)
(required)  
Remember Me?
developers.de is a .Net Community Blog powered by daenet GmbH.