Alright, after much digging and lucky discovery - it turns out that Microsoft.AspNet.Identity.Core comes with a few other interfaces, in particular, IUserRoleStore<TUser> and IUserPasswordStore<TUser> which both "inherit"(implement) IUserStore<TUser>.
So if we wanted role management capabilities, we implement IUserRoleStore<TUser>:
class MyUser : IUser
{
// Additional properties and functions not shown for brevity.
}
class MyUserStore : IUserRoleStore<MyUser>
{
public bool IsInRole(string username, string role)
{
// Implementation not show for brevity.
}
/* We would then implement the rest of the required functions.
We would have a data context here that has access to users,
user-roles, and roles.
*/
}
Now we can pass MyUserStore to UserManager<TUser>, because MyUserStore is an IUserRoleStore<TUser>, which is an IUserStore<TUser>:
UserManager<MyUser> UM = new UserManager<MyUser>(new MyUserStore());
I suspect, then, that the source code for UserManager<TUser> uses reflection to find out whether the store passed into it at constructor implements one of IUserStore<TUserStore>'s "child interfaces", in order to be able to perform role checks (if it implements IUserRoleStore<TUser>) or password set/reset (if it implements IUserPasswordStore<TUser>).
I hope you find this useful because most documentation (MVC tutorials and such) don't tell us this detail. They tell us to use the UserStore<TUser> implementation of Microsoft.AspNet.Identity.EntityFramework - where all we have to do is pass in a custom User object (that implements IUser) and we're good to go.