asp.net mvc 5 - MVC POST requests losing Authorization header - how to use API Bearer Token once retrieved -
i have spent last week creating api existing mvc application, , attempting secure api along reworking mvc side security needed.
currently, mvc application set use application cookie via owin/oauth/identity. have attempted incorporate bearer token web api set generate whenever making calls restricted api methods, have had little success far - requests work fine, post requests losing authorization header when received api.
i have created sdk client being used mvc app make calls api, , have tried total of 3 methods of setting authorization header given call api, of seem work fine requests, fail post requests need make...
i can set request header in mvc controller:
httpcontext.request.headers.add("authorization", "bearer " + response.accesstoken);
(where response.accesstoken token retrieved api)
can set request header via extension method on sdk client:
_apiclient.setbearerauthentication(token.accesstoken)
or can set request header manually on sdk client:
_apiclient.authentication = new authenticationheadervalue("bearer, accesstoken);
(where accesstoken token retrieved previously, passed client method being called).
i have little go on point causing issue. thing have been able glean far asp.net causes post requests first send in request expect header http 100-continue response, after finish actual post request. however, seems when second request, authorization header no longer present , api's authorize attribute cause 401-unauthorized response instead of running api method.
so, how take bearer token able retrieve api, , use on subsequent requests, including various post requests need make?
beyond that, best way of storing token on mvc application itself? rather avoid having pass around string every method in application need it, have been reading storing in cookie bad idea security reasons.
a few further points of interest after passed issue:
does using oauth bearer tokens mean can no longer use applicationcookies mvc application? and/or render following code useless throughout application?
user.identity.getuserid()
currently forced commenting out api [authorize] attributes in order continue work, isn't ideal allow me on things temporarily.
startup files:
mvc:
public class startup { public void configuration(iappbuilder app) { configureauth(app); } private void configureauth(iappbuilder app) { app.createperowincontext(aduidentitydbcontext.create); app.createperowincontext<aduusermanager>(aduusermanager.create); app.useoauthbearertokens(new oauthauthorizationserveroptions { accesstokenexpiretimespan = timespan.fromdays(1), //this should set false before move production. allowinsecurehttp = true, applicationcandisplayerrors = true, tokenendpointpath = new pathstring("/api/token"), }); app.usecookieauthentication(new cookieauthenticationoptions { authenticationtype = defaultauthenticationtypes.externalbearer, cookiename = "adu", expiretimespan = timespan.fromhours(2), loginpath = new pathstring("/account/login"), slidingexpiration = true, }); } }
api
public class startup { public void configuration(iappbuilder app) { httpconfiguration config = new httpconfiguration(); config.dependencyresolver = new ninjectresolver(new ninject.web.common.bootstrapper().kernel); webapiconfig.register(config); configureoauth(app); app.usecors(microsoft.owin.cors.corsoptions.allowall); app.usewebapi(config); } public void configureoauth(iappbuilder app) { app.createperowincontext(aduidentitydbcontext.create); app.createperowincontext<aduusermanager>(aduusermanager.create); oauthauthorizationserveroptions oauthserveroptions = new oauthauthorizationserveroptions() { allowinsecurehttp = true, tokenendpointpath = new pathstring("/api/token"), accesstokenexpiretimespan = timespan.fromdays(1), provider = new simpleauthorizationserverprovider(), }; //token generation app.useoauthauthorizationserver(oauthserveroptions); app.useoauthbearerauthentication(new oauthbearerauthenticationoptions()); } }
public class simpleauthorizationserverprovider : oauthauthorizationserverprovider { private iuserbusinesslogic _userbusinesslogic; /// <summary> /// creates objects necessary initialize user business logic field , initializes it, cannot done dependency injection in case. /// </summary> public void createbusinesslogic() { iuserrepository userrepo = new userrepository(); igeneratedexamrepository examrepo = new generatedexamrepository(); igeneratedexambusinesslogic exambll = new generatedexambusinesslogic(examrepo); _userbusinesslogic = new userbusinesslogic(userrepo, exambll); } public override async task validateclientauthentication(oauthvalidateclientauthenticationcontext context) { context.validated(); } public override async task grantresourceownercredentials(oauthgrantresourceownercredentialscontext context) { context.owincontext.response.headers.add("access-control-allow-origin", new[] { "*" }); //create claim user claimsidentity identity = new claimsidentity(context.options.authenticationtype); identity.addclaim(new claim("sub", user.id)); context.validated(identity); } }
after deal of time working on other aspects of project, implementing other features has made solving far easier - there response wrapper handler part of api, , part of handler saves headers incoming requests , adds them outgoing responses. believe allowing asp.net mvc side of application send authorization header again after 200-ok request sent.
i have modified authentication in order take advantage of roles, attempt exclude code should not relevant here:
mvc startup.cs:
public class startup { public void configuration(iappbuilder app) { configureauth(app); } /// <summary> /// configures authentication settings oauth. /// </summary> /// <param name="app"></param> private void configureauth(iappbuilder app) { app.createperowincontext(aduidentitydbcontext.create); app.createperowincontext<aduusermanager>(aduusermanager.create); app.useoauthbearerauthentication(new oauthbearerauthenticationoptions()); app.usecookieauthentication(new cookieauthenticationoptions { authenticationtype = defaultauthenticationtypes.applicationcookie, cookiename = "adu", expiretimespan = timespan.fromhours(2), loginpath = new pathstring("/account/login"), slidingexpiration = true }); } }
where used (accountcontroller):
private async task createlogincookie(authorizationtoken response, user result) { //create claims needed log user in //(uses usermanager several layers down in stack) claimsidentity cookieident = await _clientsdk.createclaimsidentityforuser(response.accesstoken, result, true).configureawait(false); if (cookieident == null) throw new nullreferenceexception("failed create claims cookie."); cookieident.addclaim(new claim("authtoken", response.accesstoken)); authenticationproperties authproperties = new authenticationproperties(); authproperties.allowrefresh = true; authproperties.ispersistent = true; authproperties.issuedutc = datetime.now.touniversaltime(); iowincontext context = httpcontext.getowincontext(); authenticateresult authcontext = await context.authentication.authenticateasync(defaultauthenticationtypes.applicationcookie); if (authcontext != null) context.authentication.authenticationresponsegrant = new authenticationresponsegrant(cookieident, authcontext.properties); //wrapper methods iowincontext.authentication.signout()/signin() signout(); signin(authproperties, cookieident); }
in sdk layer, created method call various other methods use reach api in order set authorization each outgoing request (i'd figure out how make attribute, i'll worry later):
private void setauthentication() { claimsidentity ident = (claimsidentity)thread.currentprincipal.identity; claim claim; //both of these methods (thread.currentprincipal, , claimsprincipal.current should work, //leaving both in sake of example. try { claim = ident.claims.first(x => x.type == "authtoken"); } catch (exception) { claim = claimsprincipal.current.claims.first(x => x.type == "authtoken"); } _apiclient.setbearerauthentication(claim.value); }
api startup.cs
/// <summary> /// configures settings used framework on application start. dependency resolver, oauth, routing, , cors /// configured. /// </summary> /// <param name="app"></param> public void configuration(iappbuilder app) { httpconfiguration config = new httpconfiguration(); config.dependencyresolver = new ninjectresolver(new bootstrapper().kernel); webapiconfig.register(config); configureoauth(app); app.usecors(corsoptions.allowall); app.usewebapi(config); } /// <summary> /// configures authentication options oauth. /// </summary> /// <param name="app"></param> public void configureoauth(iappbuilder app) { app.createperowincontext(aduidentitydbcontext.create); app.createperowincontext<aduusermanager>(aduusermanager.create); oauthauthorizationserveroptions oauthserveroptions = new oauthauthorizationserveroptions { allowinsecurehttp = true, tokenendpointpath = new pathstring("/api/token"), accesstokenexpiretimespan = timespan.fromdays(1), provider = new simpleauthorizationserverprovider() }; //token generation app.useoauthauthorizationserver(oauthserveroptions); app.useoauthbearerauthentication(new oauthbearerauthenticationoptions()); }
simpleauthorizationserverprovider.cs:
/// <summary> /// creates access bearer token , applies custom login validation logic prevent invalid login attempts. /// </summary> /// <param name="context"></param> /// <returns></returns> public override async task grantresourceownercredentials(oauthgrantresourceownercredentialscontext context) { context.owincontext.response.headers.add("access-control-allow-origin", new[] { "*" }); // performs login logic required, such accessing active directory , password validation. user user = await customloginlogic(context).configureawait(false); //if use not found, add error if 1 has not been added yet if((user == null) && !context.haserror) setinvalidgranterror(context); //break if errors have been set. if (context.haserror) return; //create claim user claimsidentity identity = new claimsidentity(context.options.authenticationtype); //add basic information claim used token. identity.addclaim(new claim("id", user?.id)); identity.addclaim(new claim("timeof", datetime.now.toshortdatestring() + " " + datetime.now.tolongtimestring())); //roles auth setroleclaim(user, ref identity); context.validated(identity); }
and finally, apparent key wraps together:
public class responsewrappinghandler : delegatinghandler { /// <summary> /// catches request before processing completed , wraps resulting response in consistent response wrapper depending on response returned api. /// </summary> /// <param name="request">the request being processed.</param> /// <param name="cancellationtoken">a cancellation token cancel processing of request.</param> /// <returns></returns> protected override async task<httpresponsemessage> sendasync(httprequestmessage request, cancellationtoken cancellationtoken) { httpresponsemessage response = await base.sendasync(request, cancellationtoken); //calls wrapping methods depending on conditions, //all of wrapping methods make call preserveheaders() } /// <summary> /// creates response based on provided request provided response's status code , request headers, , provided response data. /// </summary> /// <param name="request">the original request.</param> /// <param name="response">the reqsponse generated.</param> /// <param name="responsedata">the data include in wrapped response.</param> /// <returns></returns> private static httpresponsemessage preserveheaders(httprequestmessage request, httpresponsemessage response, object responsedata) { httpresponsemessage newresponse = request.createresponse(response.statuscode, responsedata); foreach (keyvaluepair<string, ienumerable<string>> header in response.headers) newresponse.headers.add(header.key, header.value); return newresponse; }
with of in place project able use authorization/authentication without needing client secrets , such (which 1 of goals of employer).
Comments
Post a Comment