I did not know about this for sure but I had a look in the source of Umbraco, when logging in to the backoffice there is a post to the PostLogin-method of the AuthenticationController,
Looking at like 232 it seems like there is something called UserManager that might fire an event you can subscribe to?
I made a small example. In this example the classes are created in the namespace "Umbraco86.BackOffice" but you can create your own namespace:
Create an custom class that inherrits BackOfficeUserManager :
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.Owin;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Models.Identity;
using Umbraco.Core.Security;
using Umbraco.Web.Security;
namespace Umbraco86.BackOffice
{
public class CustomBackOfficeUserManager : BackOfficeUserManager
{
public CustomBackOfficeUserManager(IUserStore<BackOfficeIdentityUser, int> store, IdentityFactoryOptions<BackOfficeUserManager> options, MembershipProviderBase membershipProvider, IContentSection contentSectionConfig) : base(store, options, membershipProvider, contentSectionConfig)
{
}
protected override void OnLoginSuccess(IdentityAuditEventArgs e)
{
// code here
int userId = e.AffectedUser;
base.OnLoginSuccess(e);
}
}
}
Then create a custom a class that inherrits from UmbracoDefaultOwinStartup to attach the created BackOfficeUserManager:
using Microsoft.Owin;
using Owin;
using Umbraco.Core.Models.Identity;
using Umbraco.Core.Security;
using Umbraco.Core.Services;
using Umbraco.Web;
using Umbraco.Web.Security;
using Umbraco86.BackOffice;
[assembly: OwinStartup("UmbracoCustomStartup", typeof(UmbracoCustomStartup))]
namespace Umbraco86.BackOffice
{
public class UmbracoCustomStartup : UmbracoDefaultOwinStartup
{
/// <summary>
/// Configures services to be created in the OWIN context (CreatePerOwinContext)
/// </summary>
/// <param name="app"/>
protected override void ConfigureServices(IAppBuilder app, ServiceContext services)
{
base.ConfigureServices(app, services);
app.ConfigureUserManagerForUmbracoBackOffice<BackOfficeUserManager, BackOfficeIdentityUser>(
RuntimeState,
GlobalSettings,
(options, context) =>
{
var membershipProvider = MembershipProviderExtensions.GetUsersMembershipProvider().AsUmbracoMembershipProvider();
var store = new BackOfficeUserStore(
services.UserService,
services.MemberTypeService,
services.EntityService,
services.ExternalLoginService,
GlobalSettings,
membershipProvider,
Mapper);
return new CustomBackOfficeUserManager(store, options, membershipProvider, UmbracoSettings.Content);
});
}
}
}
In your web.config it's necessary to change the owin:appStartup setting from
After I add it and try to login i'm getting "Login failed for user:... "
and this error..
Message":"An error has occurred.","ExceptionMessage":"The input is not a valid Base-64 string as it contains a non-base 64 character, more than two padding characters, or an illegal character among the padding characters. ","ExceptionType":"System.FormatException","StackTrace":" at System.Convert.FromBase64_ComputeResultLength(Char* inputPtr, Int32 inputLength)\r\n at System.Convert.FromBase64CharPtr(Char* inputPtr, Int32 inputLength)\r\n at System.Convert.FromBase64String(String s)\r\n at Microsoft.AspNet.Identity.Crypto.VerifyHashedPassword(String hashedPassword, String password)\r\n at Microsoft.AspNet.Identity.PasswordHasher.VerifyHashedPassword(String hashedPassword, String providedPassword)\r\n at Microsoft.AspNet.Identity.UserManager`2.<VerifyPasswordAsync>d__87.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at Umbraco.Web.Security.BackOfficeUserManager`1.<VerifyPasswordAsync>d__23.MoveNext() in d:\\a\\1\\s\\src\\Umbraco.Web\\Security\\BackOfficeUserManager.cs:line 429\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at Microsoft.AspNet.Identity.UserManager`2.<CheckPasswordAsync>d__81.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at Umbraco.Web.Security.BackOfficeUserManager`1.<CheckPasswordAsync>d__19.MoveNext() in d:\\a\\1\\s\\src\\Umbraco.Web\\Security\\BackOfficeUserManager.cs:line 380\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at Umbraco.Web.Security.BackOfficeSignInManager.<PasswordSignInAsyncImpl>d__7.MoveNext() in d:\\a\\1\\s\\src\\Umbraco.Web\\Security\\BackOfficeSignInManager.cs:line 107\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at Umbraco.Web.Security.BackOfficeSignInManager.<PasswordSignInAsync>d__6.MoveNext() in d:\\a\\1\\s\\src\\Umbraco.Web\\Security\\BackOfficeSignInManager.cs:line 57\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at Umbraco.Web.Editors.AuthenticationController.<PostLogin>d__14.MoveNext() in d:\\a\\1\\s\\src\\Umbraco.Web\\Editors\\AuthenticationController.cs:line 223\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at System.Threading.Tasks.TaskHelpersExtensions.<CastToObject>d__1`1.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at System.Web.Http.Controllers.ApiControllerActionInvoker.<InvokeActionAsyncCore>d__1.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at System.Web.Http.Filters.ActionFilterAttribute.<CallOnActionExecutedAsync>d__6.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n at System.Web.Http.Filters.ActionFilterAttribute.<CallOnActionExecutedAsync>d__6.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at System.Web.Http.Filters.ActionFilterAttribute.<ExecuteActionFilterAsyncCore>d__5.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at System.Web.Http.Filters.ActionFilterAttribute.<CallOnActionExecutedAsync>d__6.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n at System.Web.Http.Filters.ActionFilterAttribute.<CallOnActionExecutedAsync>d__6.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at System.Web.Http.Filters.ActionFilterAttribute.<ExecuteActionFilterAsyncCore>d__5.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at System.Web.Http.Controllers.ActionFilterResult.<ExecuteAsync>d__5.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at System.Web.Http.Filters.AuthorizationFilterAttribute.<ExecuteAuthorizationFilterAsyncCore>d__3.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at System.Web.Http.Dispatcher.HttpControllerDispatcher.<SendAsync>d__15.MoveNext()"}
Do you now what i'm doing wrong ?
My startup:
protected override void ConfigureServices(IAppBuilder app, ServiceContext services)
{
base.ConfigureServices(app, services);
app.ConfigureUserManagerForUmbracoBackOffice<BackOfficeUserManager, BackOfficeIdentityUser>(
RuntimeState,
GlobalSettings,
(configoptions, context) =>
{
var membershipProvider = MembershipProviderExtensions.GetMembersMembershipProvider().AsUmbracoMembershipProvider();
var store = new BackOfficeUserStore(
services.UserService,
services.MemberTypeService,
services.EntityService,
services.ExternalLoginService,
GlobalSettings,
membershipProvider,
Mapper);
return new BackofficeLogin(store, configoptions, membershipProvider, UmbracoSettings.Content);
});
// Configure hangfire
var options = new SqlServerStorageOptions { PrepareSchemaIfNecessary = true };
const string umbracoConnectionName = Umbraco.Core.Constants.System.UmbracoConnectionName;
var connectionString = System.Configuration
.ConfigurationManager
.ConnectionStrings[umbracoConnectionName]
.ConnectionString;
GlobalConfiguration.Configuration
.UseSqlServerStorage(connectionString, options)
.UseConsole();
var dashboardOptions = new DashboardOptions { Authorization = new[] { new UmbracoAuthorizationFilter() } };
// Give hangfire a URL and start the server
app.UseHangfireDashboard("/hangfire", dashboardOptions);
app.UseHangfireServer();
StartHangfireServer(app);
// Schedule jobs
var scheduler = new ScheduleHangfireJobs();
scheduler.BaselineSync();
}
And my BackofficeUserManager extened:
public class BackofficeLogin : BackOfficeUserManager
{
public BackofficeLogin(IUserStore<BackOfficeIdentityUser, int> store, IdentityFactoryOptions<BackOfficeUserManager> configoptions, MembershipProviderBase membershipProvider, IContentSection contentSectionConfig) : base(store, configoptions, membershipProvider, contentSectionConfig)
{
}
protected override void OnLoginSuccess(IdentityAuditEventArgs e)
{
// code here
int userId = e.AffectedUser;
base.OnLoginSuccess(e);
}
}
This has been the missing link, I will give it a try post haste. I have a question about convention on where you put the classes? Do you put them in the sites ~/controller folder, or ~/appcode, or in your ~/appplugin/[app_name] folder?
Is there a best practice or convention for this type of extension to the owinstartup?
That depends on what type of project you are working on or what type of developer you are.
If you are installing Umbraco without using Visual Studio then the only choice you have (as far as i know) is to put it in the App_Code folder.
If you install Umbraco via Visual Studio then you can put the classes wherever you want. You could create a folder just for your own code. In this small example i've put the classes in a folder called "BackOffice", that why the namespace is "Umbraco86.Backoffice" (the project was named "Umbraco86").
We're mostly using Umbraco as a foundation for a (pretty) large platform. We have lots of code and to keep it manageable we're using separate class libraries.
So there are many ways to manage your code, it depends of the situation.
The above solution, extending the OWIN Startup was the missing link. I had been hesitant about going down that road for fear of messing up the login procedure... Your time, effort and nudge was what I needed.
I've been developing everything in my main Umbraco project in VS. I will start looking into breaking out my more complex code into class libraries.
I'm a small (1.5 man) team, so I have the flexibility to do what i want.... which is what causes me the most difficulty. "Too many ways to skin that cat." and "Perfect is the enemy of good enough" are common themes in my development life cycles.
Thank you, sincerely for your guidance and plugging the holes in my thought.
Catch Login Event in v8
I'm trying to catch the backend login process with a composer/component...
I don't want to change the OWIN Default, I want to perform some additional login checks, after successful login.
Which services/events should I be attaching to?
Hi!
I did not know about this for sure but I had a look in the source of Umbraco, when logging in to the backoffice there is a post to the PostLogin-method of the AuthenticationController,
Looking at like 232 it seems like there is something called UserManager that might fire an event you can subscribe to?
https://github.com/umbraco/Umbraco-CMS/blob/74be0402982a303d03752f57a5208bc918ff35e5/src/Umbraco.Web/Editors/AuthenticationController.cs#L232
// m
Hi Max,
I think it is possible by creating a class that inherrrits from BackOfficeUserManager (using Umbraco.Web.Security). For example:
Best regards,
iNETZO
iNETZO, thanks for the reply:
Would I need to register a composer for this class to get it to execute on login?
Do I need to put this class in AppCode, or can it reside in my AppPlugin?
What version of Umbraco are you using?
We're running 8.5.5
I made a small example. In this example the classes are created in the namespace "Umbraco86.BackOffice" but you can create your own namespace:
Create an custom class that inherrits BackOfficeUserManager :
Then create a custom a class that inherrits from UmbracoDefaultOwinStartup to attach the created BackOfficeUserManager:
In your web.config it's necessary to change the owin:appStartup setting from
to
This is necessary to get the custom BackOfficeUserManager to be initalised.
If you run your site in debugmode and put a breakpoint in the OnLoginSuccess method, your breakpoint should be hit when you login to the backoffice.
Best regards,
iNETZO
Hey :)
I'm trying to use your code here.
After I add it and try to login i'm getting "Login failed for user:... "
and this error..
Do you now what i'm doing wrong ?
My startup:
And my BackofficeUserManager extened:
It appears to me, on the surface, that your connection string to connect to your database is corrupted in one way or another.
Have you debugged, and caught what line it fails on during authentication?
iNETZO,
This has been the missing link, I will give it a try post haste. I have a question about convention on where you put the classes? Do you put them in the sites ~/controller folder, or ~/appcode, or in your ~/appplugin/[app_name] folder?
Is there a best practice or convention for this type of extension to the owinstartup?
That depends on what type of project you are working on or what type of developer you are.
If you are installing Umbraco without using Visual Studio then the only choice you have (as far as i know) is to put it in the App_Code folder.
If you install Umbraco via Visual Studio then you can put the classes wherever you want. You could create a folder just for your own code. In this small example i've put the classes in a folder called "BackOffice", that why the namespace is "Umbraco86.Backoffice" (the project was named "Umbraco86").
We're mostly using Umbraco as a foundation for a (pretty) large platform. We have lots of code and to keep it manageable we're using separate class libraries.
So there are many ways to manage your code, it depends of the situation.
Thanks for the insight.
The above solution, extending the OWIN Startup was the missing link. I had been hesitant about going down that road for fear of messing up the login procedure... Your time, effort and nudge was what I needed.
I've been developing everything in my main Umbraco project in VS. I will start looking into breaking out my more complex code into class libraries.
I'm a small (1.5 man) team, so I have the flexibility to do what i want.... which is what causes me the most difficulty. "Too many ways to skin that cat." and "Perfect is the enemy of good enough" are common themes in my development life cycles.
Thank you, sincerely for your guidance and plugging the holes in my thought.
Max
is working on a reply...