c# - OpenIdDict and ASP.NET Core: 401 after successfully getting the token back (full repro) -


still periodically struggling openauth using openiddict (credentials flow) in asp.net core, updated latest openiddict bits , vs2017 old sample code can find @ https://github.com/myrmex/repro-oidang, full step-by-step guidance create essential startup template. hope can useful community getting started simple security scenarios, contribution simple example code welcome.

essentially followed credentials flow sample openiddict author, , can token when requesting (using fiddler):

post http://localhost:50728/connect/token content-type: application/x-www-form-urlencoded  grant_type=password&scope=offline_access profile email roles&resource=http://localhost:4200&username=zeus&password=p4ssw0rd! 

problem when try use token, keep getting 401, without other hint: no exception, nothing logged. request like:

get http://localhost:50728/api/values content-type: application/json authorization: bearer ... 

here relevant code: first startup.cs:

public void configureservices(iservicecollection services) {     // setup options di     // https://docs.asp.net/en/latest/fundamentals/configuration.html     services.addoptions();      // cors (note: if using azure, remember enable cors in portal, too!)     services.addcors();      // add entity framework , context(s) using in-memory      // (or use commented line use connection string real db)     services.addentityframeworksqlserver()         .adddbcontext<applicationdbcontext>(options =>         {             // options.usesqlserver(configuration.getconnectionstring("authentication")));             options.useinmemorydatabase();             // register entity sets needed openiddict.             // note: use generic overload if need             // replace default openiddict entities.             options.useopeniddict();         });      // register identity services     services.addidentity<applicationuser, identityrole>()         .addentityframeworkstores<applicationdbcontext>()         .adddefaulttokenproviders();      // configure identity use same jwt claims openiddict instead     // of legacy ws-federation claims uses default (claimtypes),     // saves doing mapping in authorization controller.     services.configure<identityoptions>(options =>     {         options.claimsidentity.usernameclaimtype = openidconnectconstants.claims.name;         options.claimsidentity.useridclaimtype = openidconnectconstants.claims.subject;         options.claimsidentity.roleclaimtype = openidconnectconstants.claims.role;     });      // register openiddict services     services.addopeniddict(options =>     {         // register entity framework stores         options.addentityframeworkcorestores<applicationdbcontext>();          // register asp.net core mvc binder used openiddict.         // note: if don't call method, won't able         // bind openidconnectrequest or openidconnectresponse parameters         // action methods. alternatively, can still use lower-level         // httpcontext.getopenidconnectrequest() api.         options.addmvcbinders();          // enable endpoints         options.enabletokenendpoint("/connect/token");         options.enablelogoutendpoint("/connect/logout");         // http://openid.net/specs/openid-connect-core-1_0.html#userinfo         options.enableuserinfoendpoint("/connect/userinfo");          // enable password flow         options.allowpasswordflow();         options.allowrefreshtokenflow();          // during development, can disable https requirement         options.disablehttpsrequirement();          // note: use jwt access tokens instead of default         // encrypted format, following lines required:         // options.usejsonwebtokens();         // options.addephemeralsigningkey();     });      // add framework services     services.addmvc()         .addjsonoptions(options =>         {             options.serializersettings.contractresolver =                 new newtonsoft.json.serialization.camelcasepropertynamescontractresolver();         });      // seed database demo user details     services.addtransient<idatabaseinitializer, databaseinitializer>();      // swagger     services.addswaggergen(); }  // method gets called runtime. use method configure http request pipeline. public void configure(iapplicationbuilder app, ihostingenvironment env, iloggerfactory loggerfactory,     idatabaseinitializer databaseinitializer) {     loggerfactory.addconsole(configuration.getsection("logging"));     loggerfactory.adddebug();     loggerfactory.addnlog();      // https://docs.microsoft.com/en-us/aspnet/core/fundamentals/error-handling     if (env.isdevelopment()) app.usedeveloperexceptionpage();      // serve index.html     app.usedefaultfiles();     app.usestaticfiles();      // cors     // https://docs.asp.net/en/latest/security/cors.html     app.usecors(builder =>             builder.withorigins("http://localhost:4200")                 .allowanyheader()                 .allowanymethod());      // add middleware used validate access tokens , protect api endpoints     app.useoauthvalidation();      app.useopeniddict();      app.usemvc();      // app.usemvcwithdefaultroute();     // app.usewelcomepage();      // seed database     databaseinitializer.seed().getawaiter().getresult();      // swagger     // enable middleware serve generated swagger json endpoint     app.useswagger();     // enable middleware serve swagger-ui assets (html, js, css etc.)     app.useswaggerui(); } 

and controller (you can find whole solution in repository quoted above):

public sealed class authorizationcontroller : controller {     private readonly ioptions<identityoptions> _identityoptions;     private readonly signinmanager<applicationuser> _signinmanager;     private readonly usermanager<applicationuser> _usermanager;      public authorizationcontroller(         ioptions<identityoptions> identityoptions,         signinmanager<applicationuser> signinmanager,         usermanager<applicationuser> usermanager)     {         _identityoptions = identityoptions;         _signinmanager = signinmanager;         _usermanager = usermanager;     }      private async task<authenticationticket> createticketasync(openidconnectrequest request, applicationuser user)     {         // create new claimsprincipal containing claims         // used create id_token, token or code.         claimsprincipal principal = await _signinmanager.createuserprincipalasync(user);          // create new authentication ticket holding user identity.         authenticationticket ticket = new authenticationticket(             principal, new authenticationproperties(),             openidconnectserverdefaults.authenticationscheme);          // set list of scopes granted client application.         // note: offline_access scope must granted         // allow openiddict return refresh token.         ticket.setscopes(new[] {             openidconnectconstants.scopes.openid,             openidconnectconstants.scopes.email,             openidconnectconstants.scopes.profile,             openidconnectconstants.scopes.offlineaccess,             openiddictconstants.scopes.roles         }.intersect(request.getscopes()));          ticket.setresources("resource-server");          // note: default, claims not automatically included in access , identity tokens.         // allow openiddict serialize them, must attach them destination, specifies         // whether should included in access tokens, in identity tokens or in both.         foreach (var claim in ticket.principal.claims)         {             // never include security stamp in access , identity tokens, it's secret value.             if (claim.type == _identityoptions.value.claimsidentity.securitystampclaimtype)                 continue;              list<string> destinations = new list<string>             {                 openidconnectconstants.destinations.accesstoken             };              // add iterated claim id_token if corresponding scope granted client application.             // other claims added access_token, encrypted when using default format.             if (claim.type == openidconnectconstants.claims.name &&                 ticket.hasscope(openidconnectconstants.scopes.profile) ||                 claim.type == openidconnectconstants.claims.email &&                 ticket.hasscope(openidconnectconstants.scopes.email) ||                 claim.type == openidconnectconstants.claims.role &&                 ticket.hasscope(openiddictconstants.claims.roles))             {                 destinations.add(openidconnectconstants.destinations.identitytoken);             }              claim.setdestinations(openidconnectconstants.destinations.accesstoken);         }          return ticket;     }      [httppost("~/connect/token"), produces("application/json")]     public async task<iactionresult> exchange(openidconnectrequest request)     {         // if prefer not bind request parameter, can still use:         // openidconnectrequest request = httpcontext.getopenidconnectrequest();          debug.assert(request.istokenrequest(),             "the openiddict binder asp.net core mvc not registered. " +             "make sure services.addopeniddict().addmvcbinders() correctly called.");          if (!request.ispasswordgranttype())         {             return badrequest(new openidconnectresponse             {                 error = openidconnectconstants.errors.unsupportedgranttype,                 errordescription = "the specified grant type not supported."             });         }          applicationuser user = await _usermanager.findbynameasync(request.username);         if (user == null)         {             return badrequest(new openidconnectresponse             {                 error = openidconnectconstants.errors.invalidgrant,                 errordescription = "the username/password couple invalid."             });         }          // ensure user allowed sign in.         if (!await _signinmanager.cansigninasync(user))         {             return badrequest(new openidconnectresponse             {                 error = openidconnectconstants.errors.invalidgrant,                 errordescription = "the specified user not allowed sign in."             });         }          // reject token request if two-factor authentication has been enabled user.         if (_usermanager.supportsusertwofactor && await _usermanager.gettwofactorenabledasync(user))         {             return badrequest(new openidconnectresponse             {                 error = openidconnectconstants.errors.invalidgrant,                 errordescription = "the specified user not allowed sign in."             });         }          // ensure user not locked out.         if (_usermanager.supportsuserlockout && await _usermanager.islockedoutasync(user))         {             return badrequest(new openidconnectresponse             {                 error = openidconnectconstants.errors.invalidgrant,                 errordescription = "the username/password couple invalid."             });         }          // ensure password valid.         if (!await _usermanager.checkpasswordasync(user, request.password))         {             if (_usermanager.supportsuserlockout)                 await _usermanager.accessfailedasync(user);              return badrequest(new openidconnectresponse             {                 error = openidconnectconstants.errors.invalidgrant,                 errordescription = "the username/password couple invalid."             });         }          if (_usermanager.supportsuserlockout)             await _usermanager.resetaccessfailedcountasync(user);          // create new authentication ticket.         authenticationticket ticket = await createticketasync(request, user);          var result = signin(ticket.principal, ticket.properties, ticket.authenticationscheme);         return result;         // return signin(ticket.principal, ticket.properties, ticket.authenticationscheme);     }      [httpget("~/connect/logout")]     public async task<iactionresult> logout()     {         // extract authorization request asp.net environment.         openidconnectrequest request = httpcontext.getopenidconnectrequest();          // ask asp.net core identity delete local , external cookies created         // when user agent redirected external identity provider         // after successful authentication flow (e.g google or facebook).         await _signinmanager.signoutasync();          // returning signoutresult ask openiddict redirect user agent         // post_logout_redirect_uri specified client application.         return signout(openidconnectserverdefaults.authenticationscheme);     }      // http://openid.net/specs/openid-connect-core-1_0.html#userinfo     [authorize]     [httpget("~/connect/userinfo")]     public async task<iactionresult> getuserinfo()     {         applicationuser user = await _usermanager.getuserasync(user);          // simplify, in demo have 1 role users: either admin or editor         string srole = await _usermanager.isinroleasync(user, "admin")             ? "admin"             : "editor";          // http://openid.net/specs/openid-connect-core-1_0.html#standardclaims         return ok(new         {             sub = user.id,             given_name = user.firstname,             family_name = user.lastname,             name = user.username,             user.email,             email_verified = user.emailconfirmed,             roles = srole         });     } } 

as mentioned in blog post, token format used openiddict changed recently, makes tokens issued latest openiddict bits incompatible old oauth2 validation middleware version you're using.

migrate aspnet.security.oauth.validation 1.0.0 , should work.


Comments

Popular posts from this blog

Command prompt result in label. Python 2.7 -

javascript - How do I use URL parameters to change link href on page? -

amazon web services - AWS Route53 Trying To Get Site To Resolve To www -