asp.net - using simple injector in mvc6 with cookie auth -


i have mvc6 project using simple injector , cookie middleware authentication without asp.net identity (tutorials below)

http://simpleinjector.readthedocs.org/en/latest/aspnetintegration.html http://docs.asp.net/en/latest/security/authentication/cookie.html

i have custom signinmanager / usermanager wraps principalcontext validate windows credentials (sidenote: not using azure ad aspnet 5 because [in future] know there mix of windows , non windows usernames. plus not permissions in enough time). initial issue injecting ihttpcontextaccessor signinmanager , cookieauthenticationoptions both classes. kept receiving error below:

no authentication handler configured handle scheme: thiscompany.identity

to solve issue had ihttpcontextaccessor asp.net services , register simple injector. worked, seemed wrong , maybe there way it. so, wrong? if so, hoping others have tried , can chime in solution if exists. below abbreviated versions of classes:

  public class startup   {     public static iconfigurationroot configuration;     private readonly container container = new container();     private readonly appsettings settings;     private readonly cookieauthenticationoptions cookieoptions;      public startup(ihostingenvironment env, iapplicationenvironment appenv)     {       // config builder here...        cookieoptions = createcookieoptions();     }      public void configureservices(iservicecollection services)     {       // other stuff here...        services.addinstance<icontrolleractivator>(new simpleinjectorcontrolleractivator(container));       services.addinstance<iviewcomponentinvokerfactory>(new simpleinjectorviewcomponentinvokerfactory(container));       services.add(servicedescriptor.instance<ihttpcontextaccessor>(new nevernullhttpcontextaccessor()));     }      public async void configure(iapplicationbuilder app, iloggerfactory loggerfactory, ihostingenvironment env)     {       app.usecookieauthentication(cookieoptions);        #region di        container.options.defaultscopedlifestyle = new aspnetrequestlifestyle();       container.options.lifestyleselectionbehavior = new scopelifestyleselectionbehavior();       app.usesimpleinjectoraspnetrequestscoping(container);       initializecontainer(app);        // part unsure       var accessor = app.applicationservices.getrequiredservice<ihttpcontextaccessor>();                           container.register(() => accessor, lifestyle.scoped);        container.registeraspnetcontrollers(app);       container.registeraspnetviewcomponents(app);       container.verify();        #endregion        using (var scope = simpleinjectorexecutioncontextscopeextensions.beginexecutioncontextscope(container))       {         // seed cache , dummy data       }     }      private void initializecontainer(iapplicationbuilder app)     {       var conn = new sqlconnection(configuration["data:appmainconnection"]);        // bunch of registrations...        container.registersingleton(() => cookieoptions);     }      private sealed class nevernullhttpcontextaccessor : ihttpcontextaccessor     {       private readonly asynclocal<httpcontext> context = new asynclocal<httpcontext>();        public httpcontext httpcontext       {         { return context.value ?? new defaulthttpcontext(); }         set { context.value = value; }       }     }      private sealed class scopelifestyleselectionbehavior : ilifestyleselectionbehavior     {       public lifestyle selectlifestyle(type servicetype, type implementationtype)       {         return lifestyle.scoped;       }     }     private cookieauthenticationoptions createcookieoptions()     {       return new cookieauthenticationoptions()       {         authenticationscheme = "thiscompany.identity",         automaticchallenge = true,         automaticauthenticate = true,         loginpath = new pathstring("/auth/login/"),         logoutpath = new pathstring("/auth/logout"),         accessdeniedpath = new pathstring("/auth/forbidden/"), // todo         cookiename = "yumyum.net",         slidingexpiration = true,         expiretimespan = timespan.fromdays(1),         events = new cookieauthenticationevents()         {           onredirecttoaccessdenied = ctx =>           {             if (ctx.request.path.startswithsegments("/api") && ctx.response.statuscode == 200)             {               ctx.response.statuscode = (int)httpstatuscode.unauthorized;             }             else             {               ctx.response.redirect(ctx.redirecturi);             }              return task.fromresult(0);           }         }       };     } 

and here signinmanager (i won't show usermanager, wraps repo,principalcontext , claims creation:

  public class signinmanager : isigninmanager   {     private readonly iusermanager usermanager;     private readonly httpcontext context;     private readonly cookieauthenticationoptions options;      public signinmanager(ihttpcontextaccessor contextaccessor, iusermanager usermanager, cookieauthenticationoptions options)     {       if (contextaccessor == null || contextaccessor.httpcontext == null)       {         throw new argumentnullexception(nameof(contextaccessor));       }       if (options == null) throw new argumentnullexception(nameof(options));          if (usermanager == null) throw new argumentnullexception(nameof(usermanager));        context = contextaccessor.httpcontext;       this.usermanager = usermanager;       this.options = options;     }      public async task<bool> passwordsigninasync(string user, string password, bool ispersistent)     {       if (user == null) throw new argumentnullexception(nameof(user));        if (await usermanager.checkpasswordasync(user, password))       {         await signinasync(user, ispersistent);         return true;       }        return false;     }      public async task signoutasync() => await context.authentication.signoutasync(options.authenticationscheme);      private async task signinasync(string user, bool ispersistent)     {       var authenticationproperties = new authenticationproperties { ispersistent = ispersistent };       var userprincipal = await usermanager.createuserprincipalasync(user);       if (userprincipal == null) throw new invalidoperationexception($"{user} not found");          // error happening         await context.authentication.signinasync(options.authenticationscheme,           new claimsprincipal(userprincipal),           authenticationproperties);      }   } 

update

here details when added container.crosswire<ihttpcontextaccessor>(app); , remove

  var accessor = app.applicationservices.getrequiredservice<ihttpcontextaccessor>();                       container.register(() => accessor, lifestyle.scoped); 

exception: isigninmanager injected authcontroller scoped since authcontroller scoped too:

simpleinjector.diagnosticverificationexception unhandled   hresult=-2146233088   message=the configuration invalid. following diagnostic warnings reported: -[lifestyle mismatch] signinmanager (asp.net request) depends on ihttpcontextaccessor (transient). see error property detailed information warnings. please see https://simpleinjector.org/diagnostics how fix problems , how suppress individual warnings.   source=simpleinjector   stacktrace:        @ simpleinjector.container.throwondiagnosticwarnings()        @ simpleinjector.container.verify(verificationoption option)        @ simpleinjector.container.verify()        @ startup.<configure>d__7.movenext() in ... line 109     --- end of stack trace previous location exception thrown ---        @ system.runtime.compilerservices.asyncmethodbuildercore.<>c.<throwasync>b__6_1(object state)        @ system.threading.queueuserworkitemcallback.waitcallback_context(object state)        @ system.threading.executioncontext.runinternal(executioncontext executioncontext, contextcallback callback, object state, boolean preservesyncctx)        @ system.threading.executioncontext.run(executioncontext executioncontext, contextcallback callback, object state, boolean preservesyncctx)        @ system.threading.queueuserworkitemcallback.system.threading.ithreadpoolworkitem.executeworkitem()        @ system.threading.threadpoolworkqueue.dispatch()        @ system.threading._threadpoolwaitcallback.performwaitcallback()   innerexception: 

update

i sure corrected if not correct, went @steven's answer adapter. guess more of lesson of design patterns not familiar with. here new class , registration below use in custom signinmanager:

  public class defaultauthenticationmanager : iauthenticationmanager   {     private readonly httpcontext context;      public defaultauthenticationmanager(ihttpcontextaccessor accessor)     {       if (accessor == null || accessor.httpcontext == null) throw new argumentnullexception(nameof(accessor));        context = accessor.httpcontext;     }      public task signinasync(string authenticationscheme, claimsprincipal principal, authenticationproperties properties)     {       return context.authentication.signinasync(authenticationscheme, principal, properties);     }      public task signoutasync(string authenticationscheme)     {      return  context.authentication.signoutasync(authenticationscheme);     }   }   private void initializecontainer(iapplicationbuilder app) {      var accessor = app.applicationservices.getrequiredservice<ihttpcontextaccessor>();       container.register<iauthenticationmanager>(() => new defaultauthenticationmanager(accessor), lifestyle.scoped); } 

the crosswire extension method in integration package makes delegate registration in simple injector allows simple injector 'know' service, while asp.net 5 configuration system still in control of building service. can same follows:

container.register(() => app.applicationservices.getrequiredservice<isomeservice>()); 

the crosswire extension method seems rather useless, since seems one-liner yourself, crosswire 1 thing, suppressing diagnostic warning thrown when transient component implements idisposable. works around design flaws in asp.net 5, because there abstractions in asp.net 5 implement idisposable, while abstractions should never implement idisposable (abstractions do, violate dependency inversion principle).

but brings me next point, crosswire makes registration in simple injector transient, though in asp.net registration might scoped or singleton. lifestyle component has in asp.net implementation detail, , might change time time. or @ least, lifestyle unknown both user , simple injector. that's why safest give cross-wired registrations transient lifestyle default. mean dependent application components should transient prevent captive dependencies (a.k.a. lifestyle mismatches). typically not problem, because application components depend asp.net services asp.net related. it's unlikely have core application components depend on asp.net stuff, because violate dependency inversion principle , might lead hard maintain code.

in case can few things. simplest thing make signinmanager transient well. seems unlikely has state should maintain on single request, , when does, state should not belong there anyway (single responsibility violation).

another option cross wire ihttpcontextaccessor singleton in simple injector. valid, because service registered singleton in asp.net well. won't cause hidden captive dependencies (unless microsoft changes lifetime in future; in case we're screwed). can this:

container.registersingleton(app.applicationservices.getrequiredservice<ihttpcontextaccessor>()); 

your third option prevent registration of ihttpcontextaccessor completely. dependency inversion principle violation application code. it's dip violation, because ihttpcontextaccessor not defined application framework. therefore never defined in way suits application needs. application code hardly ever need httpcontext object. rather interested in particular value such userid, tenantid, or other contextual value. instead, application better of when depends on iusercontext, itenantcontext or other specific abstraction. whether or not value extracted httpcontext implementation detail.

such implementation (adapter) can resolve ihttpcontextaccessor @ runtime , httpcontext it. implementation of such adapter of time simple of course, fine; our goal shield application knowledge. since adapter has knowledge asp.net abstraction, fine resolve services configuration. adapter merely anti-corruption layer.

these options.


Comments

Popular posts from this blog

ruby - Trying to change last to "x"s to 23 -

jquery - Clone last and append item to closest class -

c - Unrecognised emulation mode: elf_i386 on MinGW32 -