Need help debugging JwtSecurityTokenHandler (web api owin auth / resource servers) - asp.net-web-api2

I am desperately trying to find out why I keep getting a 401 response from my resource server:
Scenario:
Console program running an authorization server.
Console program running a resource server.
Console program using both (client).
I have no problem getting the token, and it validates with expected content at jwt.io. Further, when submitting the base64 encoded secret used to create the token, the site also validates the token string.
I have tried every possible combination of suppressing and/or adding HostAuthentication defaults/filters; I have used UseCors (AllowAll) before and after both auth and webapi use-calls; I have tried juuuuust about anything!
This is my configure auth:
private void ConfigureAuth(IAppBuilder app, HttpConfiguration config)
{
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter("Bearer"));
var issuer = ConfigurationManager.AppSettings["tokenIssuer"];
var audience = "MyEnergy"; //TODO: audience should be taken from somewhere else!!
var secret = TextEncodings.Base64Url.Decode(ConfigurationManager.AppSettings["secret"]); //TODO: no configuration manager here!
var jwtBearerOptions = new JwtBearerAuthenticationOptions()
{
TokenHandler = new testdumbass(),
AuthenticationMode = Microsoft.Owin.Security.AuthenticationMode.Active,
AuthenticationType = "JWT",
AllowedAudiences = new[] {audience},
IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
{new SymmetricKeyIssuerSecurityTokenProvider(issuer, secret)}
};
app.UseJwtBearerAuthentication(jwtBearerOptions);
}
and here is my Configuration and ConfigureWebApi:
public void Configuration(IAppBuilder app)
{
var config = new HttpConfiguration();
ConfigureAuth(app, config); //using oauth
ConfigureWebApi(config);
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
app.UseWebApi(config);
}
private void ConfigureWebApi(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
"Default",
"{controller}/{id}",
new {id = RouteParameter.Optional});
}
So, I tried to implement a custom JwtSecurityTokenHandler (testdumbass class) and just overrided everything I could, calling base operations. From what I can see, the default implementation has NO problem reading the token (all the expected values are there).
So how can I test the validation?
The following three methods are called (in that order):
ReadToken
CanReadToken
ValidateToken (out validatedToken)
*ValidateSignature
*ReadToken
*CanReadToken
*ValidateIssuerSecurityKey
*ValidateLifetime
*ValidateAudience
*ValidateIssuer
*CreateClaimsIdentity
ValidateToken finishes
Now the out parameter looks fine. SecurityKeys however are 0 and Id is null (any relevance?)
The inner JwtSecurityToken has a signing key, where the 32 byte array matches what I would expect.
If I look at non-public members allthe 4 rawXXX field have values (Data, Header and Payload as expected (dunno what to make of rawSignature))
I am using "https://localhost" as issuer, which can also be read from the validated token.
After all of this, I have overriden OnAuthorizationAsync in my own custom AuthorizeAttribute. Here I call the base implementation (the one in AuthorizeAttribute) and the actionContext always fails with 401.
I must admit I really cannot figure out WHY!

I found out what the problem was:
I took these two lines from here:
private void ConfigureAuth(IAppBuilder app, HttpConfiguration config)
{
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter("Bearer"));
and moved them here:
private void ConfigureWebApi(HttpConfiguration config)
{
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter("Bearer"));
config.MapHttpAttributeRoutes();
This would result in the correct context with correct identity in the AuthorizeAttribute validation (in the HttpActionContext sent as parameter).
What I dont get, is why changes in the config matter before using it in the UseWebApi call?

Related

Wanting to have seperate policies for SignIn and SignUp while using MSAL.net - both receiving webhook for AuthorizationCodeReceived

I have a .net mvc project based on b2c-webapi-dotnet ([https://github.com/Azure-Samples/active-directory-b2c-dotnet-webapp-and-webapi][1]) where I can register users or allow them to sign in via b2c (both operations having the same SignUpSignIn policy). This works as I expect.
I'm now trying to split this single policy into two, so having a SignUp policy and an additional SignIn policy
As part of this, both policies need to hit the AuthorizationCodeReceived hook so that I can pull out the b2c oid guid. This is used to find additional information on a user stored in a separate database.
What I am finding is that OnAuthorizationCodeReceived is only called when using one of my policies, its the default policy that I set into the MetadataAddress property when setting up the authorization.
In the code below, whichever policy I set for Settings.B2C_DefaultPolicyId (either SignIn or SignUp) is the one that has OnAuthorizationCodeReceived called.
Can anyone tell me if there is a way to have both policies be able to call OnAuthorizationCodeReceived ?
I appreciate any help you can give.
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
// ASP.NET web host compatible cookie manager
CookieManager = new SystemWebChunkingCookieManager()
});
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
// Generate the metadata address using the tenant and policy information
MetadataAddress = String.Format(WellKnownMetadata, Common.Settings.AppSettings.B2C_Tenant, Settings.B2C_DefaultPolicyId),
// These are standard OpenID Connect parameters, with values pulled from web.config
ClientId = Settings.AppSettings.B2C_ClientID,
RedirectUri = RedirectUri,
PostLogoutRedirectUri = PostLogoutRedirectUri,
// Specify the callbacks for each type of notifications
Notifications = new OpenIdConnectAuthenticationNotifications
{
RedirectToIdentityProvider = OnRedirectToIdentityProvider,
AuthorizationCodeReceived = OnAuthorizationCodeReceived,
AuthenticationFailed = OnAuthenticationFailed,
},
// Specify the claim type that specifies the Name property.
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
ValidateIssuer = false
},
// Specify the scope by appending all of the scopes requested into one string (separated by a blank space)
Scope = $"openid profile offline_access {Settings.AppSettings.B2C_ReadTasksScope} {Settings.AppSettings.B2C_WriteTasksScope}",
// ASP.NET web host compatible cookie manager
CookieManager = new SystemWebCookieManager()
}
);
}
I've figured it out with the help of a colleague
From the example code I had, one of the callbacks had some code that needed to be updated
private Task OnRedirectToIdentityProvider(RedirectToIdentityProviderNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
{
var policy = notification.OwinContext.Get<string>("Policy");
if (!string.IsNullOrEmpty(policy) && !policy.Equals(Globals.DefaultPolicy))
{
notification.ProtocolMessage.Scope = OpenIdConnectScope.OpenId;
notification.ProtocolMessage.ResponseType = OpenIdConnectResponseType.IdToken;
notification.ProtocolMessage.IssuerAddress = notification.ProtocolMessage.IssuerAddress.ToLower().Replace(Globals.DefaultPolicy.ToLower(), policy.ToLower());
}
return Task.FromResult(0);
}
The ResponseType needs to be changed from IdToken to CodeIdToken. Doing this means the non default Policy also hits the callback AuthorizationCodeReceived
One side affect of doing this is that after OnAuthorizationCodeReceived is hit, the callback for AuthenticationFailed is also hit.
To handle this, I've just put a conditional statement in the OnAuthenticationFailed method that returns to the root view (notification.Response.Redirect("/"))

How can I use Asp.Net Core 2.0's in-memory TestServer class for Integration Tests when my API requires an Authorization Token?

I am working on an ASP.NET Core 2.0 Web API and I want to do some integration tests using ASP.NET Core's TestServer class. I am using xUnit as my testing framework so I have created a TestServerFixture class that creates the in-memory TestServer instance and then use the TestServer's .CreateClient() to create the HTTPClient instance.
My Web API requires an OAuth2.0 Access Token from my Azure AD. I set this up using this code in my Startup.cs, ConfigureServices method:
// Add Azure AD OAUTH2.0 Authentication Services
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddAzureAdBearer(options => Configuration.Bind("AzureAd", options));
and in my controllers, I have the [Authorize] attribute on the class.
So for my Integration Tests setup, I have a method in my TestServerFixture that obtains a valid token from Azure AD and I add it to my client request header as follows;
Client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", await _testServerFixture.GetAccessToken());
When I debug my integration test, I can see that the request does contain a valid access token but I am still getting a 401 Unauthorized from the API when I run my Integration Test.
After doing some digging I found several resources that talk about a similar issue with TestServer, but related to Authentication rather than Authorization, as I am experiencing. Here are links to these resources;
https://medium.com/#zbartl/authentication-and-asp-net-core-integration-testing-using-testserver-15d47b03045a
How do I integration test a ASP 5/Core Web API with [Authorize] Attributes
http://geeklearning.io/how-to-deal-with-identity-when-testing-an-asp-net-core-application/
These all talk about assigning a ClaimsPrincipal to the context.user using custom middleware. Since this is based upon Authentication rather than Authorization, I am not sure if I can do something similar for my Access Token.
I do know that in my API, I can access the HTTPContext.User and pull out the AppId value, which is part of the Access Token so it would seem that Authentication and Authorization both use the Context.User.
So, before I burn time building up my own custom middleware for this purpose, I wanted to see if anyone has already addressed this issue or perhaps are aware of a NuGet that does what I need.
EDIT - SOLUTION
I am showing this in case anyone else runs into this issue.
I ended up building the middleware that Zach Bartlett presented in his blog , but making the following changes.
public class AuthenticatedTestRequestMiddleware
{
#region Class Variables
private const string TestingAccessTokenAuthentication = "TestingAccessTokenAuthentication";
private readonly RequestDelegate _next;
#endregion Class Variables
#region Constructor(s)
public AuthenticatedTestRequestMiddleware(RequestDelegate next)
{
_next = next;
}
#endregion Constructor(s)
public async Task Invoke(HttpContext context)
{
if (context.Request.Headers.Keys.Contains("X-Integration-Testing"))
{
if (context.Request.Headers.Keys.Contains("Authorization"))
{
var token = context.Request.Headers["Authorization"].First();
var claimsIdentity = new ClaimsIdentity(new List<Claim>
{
new Claim(ClaimTypes.Authentication, token)
}, TestingAccessTokenAuthentication);
var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
context.User = claimsPrincipal;
}
}
await _next(context);
}
}
There were one interesting "Gotcha".
In Zach's blog he had the code;
public const string TestingHeader = "X-Integration-Testing";
at the top of his middleware and then references the TestingHeader in the test for the key in the header collection like this;
if (context.Request.Headers.Keys.Contains(TestingHeader)
Doing it this way was failing for me until I put the string literal instead of the variable into the .Contains() clause.
Now, my integration test is passing with a 200 OK response. :)
I was able to find a solution following Zach Bartlett's blog post, and making some small changes to make it pertain to the Authentication header. The code is shown as an edit in my original post above.

Picture claim not available when returning to ExternalCallback

I'm in the process of upgrading two asp.net core 1.1 identityserver applications to asp.net core 2.
The first one, IdentityServer A is acting as a federated gateway and identity store. The second one, IdentityServer B is using A as external provider. In my app domain, clients should authenticate against B which puts additional claims on the identity which is received from A.
The authentication flow seems to be working fine at this point, except when B is adding additional claims to the identity.
On A the IdentityServer is configured as follows:
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddInMemoryClients(Clients.GetClients())
.AddInMemoryIdentityResources(IdentityResources.GetIdentityResources())
.AddAspNetIdentity<ApplicationUser>()
.AddProfileService<ProfileService>();
The ProfileService on A is implemented like so:
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
var user = await _userManager.GetUserAsync(context.Subject);
// Scope: OpenId
AddClaimIfRequested(JwtClaimTypes.Subject, context.RequestedClaimTypes, context.IssuedClaims, user.Id);
// Scope: Profile
AddClaimIfRequested(JwtClaimTypes.GivenName, context.RequestedClaimTypes, context.IssuedClaims, user.Firstname);
AddClaimIfRequested(JwtClaimTypes.FamilyName, context.RequestedClaimTypes, context.IssuedClaims, user.Lastname);
AddClaimIfRequested(JwtClaimTypes.Picture, context.RequestedClaimTypes, context.IssuedClaims, Convert.ToBase64String(user.ProfilePicture));
// Scope: Email
AddClaimIfRequested(JwtClaimTypes.Email, context.RequestedClaimTypes, context.IssuedClaims, user.Email);
}
private void AddClaimIfRequested(string claim, IEnumerable<string> requestedClaims, List<Claim> issuedClaims, string value)
{
if (requestedClaims.Contains(claim))
issuedClaims.Add(new Claim(claim, value));
}
On B the ExternalCallback from A is handled in the AccountController.ExternalCallback:
public async Task<IActionResult> ExternalCallback(string returnUrl)
{
//Read external identity from the tempoary cookie
var result = await HttpContext.AuthenticateAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme);
if (result == null)
throw new Exception("External authentication error");
var externalUser = result.Principal;
if (externalUser == null)
throw new Exception("External authentication error");
//Get external claims
var claims = externalUser.Claims.ToList();
... additional identity handling and signin on B.
At this point, claims contains no picture claim. The other profile related claims that has been set in the ProfileService of A are all present. If I hardcode some other claim values in the ProfileService I see the values reflected in ExternalCallback as expected. But the picture claim isn't there. Furthermore the context.RequestedClaimTypes does contain "profile" when GetProfileDataAsync is being executed.
I've tried providing a hardcoded value for the picture claim (an url pointing to a png file (which seems to be the right way of doing it)), but no difference.
Since the picture value is a base64 encoded image file, I've also tried to increase the MaxResponseBufferSize for Kestrel, but that didn't make any difference either.
Since the claim values are being set in the ProfileService I guess it must be somewhere before the call to
var result = await HttpContext.AuthenticateAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme);
Why the picture claim isn't there I can't figure out. Any suggestions are welcome.
EDIT
Futher info. On B where .AddOpenIdConnect("oidc"... are defined, I've tried adding the OnUserInformationReceived event to options.Events. When the user information is received I can see the picture property as part of the User object on the context. So the information is available, but doesn't get added as a claim... it seems.

Is is possible to disable authentication providers for specific routes?

We're evaluating service stack v.4.5.6.0 for a Web API and we want clients to be able to authenticate using basic auth or credentials but we do not want them to be able to provide a basic auth header in place of a JWT token or session cookie when using our services. While I realize this is somewhat arbitrary, is there a way to exclude routes from specific providers or force the use of a token/cookie to authenticate once they've logged in?
Auth config from AppHost:
private void ConfigureAuth(Container container)
{
var appSettings = new AppSettings();
this.Plugins.Add(new AuthFeature(() => new CustomAuthUserSession(),
new IAuthProvider[]
{
new CredentialsAuthProvider(),
new BasicAuthProvider(),
new JwtAuthProvider(appSettings)
}) { IncludeAssignRoleServices = false, MaxLoginAttempts = 10} );
var userRepository = new CustomUserAuthRepository(container.TryResolve<IDbConnectionFactory>());
container.Register<IAuthRepository>(userRepository);
}
ServiceStack lets you decide which AuthProviders you want your Services to be authenticated with, but it doesn't let you individually configure which adhoc AuthProviders applies to individual Services. Feel free to add this a feature request.
However if you want to ensure that a Service is only accessed via JWT you can add a check in your Services for FromToken which indicates the Session was populated by a JWT Token, e.g:
[Authenticate]
public class MyServices : Service
{
public object Any(MyRequest request)
{
var session = base.SessionAs<AuthUserSession>();
if (!session.FromToken)
throw HttpError.Unauthorized("Requires JWT Authentication");
//...
}
}
From v4.5.7 that's now available on MyGet you can also use the new session.AuthProvider property which indicates what AuthProvider was used to Authenticate the user, e.g:
public object Any(MyRequest request)
{
var session = base.SessionAs<AuthUserSession>();
if (session.AuthProvider != JwtAuthProvider.Name)
throw HttpError.Unauthorized("Requires JWT Authentication");
//...
}
Refer to the docs for different AuthProvider names for each AuthProvider.

.NET CORE API Making Facebook Login Work With Openiddict/Identity

I have one project (Project A) which is a .NET CORE API project using Openiddict with an endpoint of /connect/token to issue JWT tokens using Identity to handle the security etc. This project works great as is.
I have another project (Project B), which is just a very simple project with some HTML that makes requests to the API to get an access token, and get data from the API. This project also works great.
Now the part I cannot wrap my brain around, how do I use Facebook login between these two totally separate projects? I know how to use it if everything is under one roof, and it's really easy, but this scenario has me totally confused since everything is separated. So for starters, who handles the 'ExternalLogin', 'ExternalLoginCallBack' logic (from .NET web template using individual accounts), the API? The HTML project? When connecting with Facebook, what redirect uri should I use (API/HTML project)? Then who should have the below code in their 'Startup.cs' file?
app.UseFacebookAuthentication(new FacebookOptions
{
AppId = "xxxxxxx",
AppSecret = "xxxxxxxxx",
Scope = { "email", "user_friends" },
Fields = { "name", "email" },
SaveTokens = true,
});
And finally if this helps here is how I have Project A currently setup:
STARTUP.CS (API)
public void ConfigureServices function: (API)
// add entity framework using the config connection string
services.AddEntityFrameworkSqlServer()
.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
// add identity
services.AddIdentity<ApplicationUser, ApplicationRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
// add OpenIddict
services.AddOpenIddict<ApplicationUser, ApplicationRole, ApplicationDbContext>()
.DisableHttpsRequirement()
.EnableTokenEndpoint("/connect/token")
.AllowPasswordFlow()
.AllowRefreshTokenFlow()
.UseJsonWebTokens()
.AddEphemeralSigningKey();
services.AddCors();
public void Configure function: (API)
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
RequireHttpsMetadata = false,
Audience = "http://localhost:54418/",
Authority = "http://localhost:54418/"
});
Authorization Controller (API)
public class AuthorizationController : Controller
{
private OpenIddictUserManager<ApplicationUser> _userManager;
public AuthorizationController(OpenIddictUserManager<ApplicationUser> userManager)
{
_userManager = userManager;
}
[HttpPost("~/connect/token")]
[Produces("application/json")]
public async Task<IActionResult> Exchange()
{
var request = HttpContext.GetOpenIdConnectRequest();
if (request.IsPasswordGrantType())
{
var user = await _userManager.FindByNameAsync(request.Username);
if (user == null)
{
return BadRequest(new OpenIdConnectResponse
{
ErrorDescription = "The username or password provided is incorrect"
});
}
var identity = await _userManager.CreateIdentityAsync(user, request.GetScopes());
// Add a custom claim that will be persisted
// in both the access and the identity tokens.
if (user.Avatar != null)
{
identity.AddClaim("user_avatar", user.Avatar,
OpenIdConnectConstants.Destinations.AccessToken,
OpenIdConnectConstants.Destinations.IdentityToken);
}
if (user.InSiteUserName != null)
{
identity.AddClaim("insite_username", user.InSiteUserName,
OpenIdConnectConstants.Destinations.AccessToken,
OpenIdConnectConstants.Destinations.IdentityToken);
}
identity.AddClaim("hasLoggedIn", user.HasLoggedIn.ToString(),
OpenIdConnectConstants.Destinations.AccessToken,
OpenIdConnectConstants.Destinations.IdentityToken);
// Create a new authentication ticket holding the user identity.
var ticket = new AuthenticationTicket(
new ClaimsPrincipal(identity),
new AuthenticationProperties(),
OpenIdConnectServerDefaults.AuthenticationScheme);
ticket.SetResources(request.GetResources());
ticket.SetScopes(request.GetScopes());
return SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme);
}
return BadRequest(new OpenIdConnectResponse
{
Error = OpenIdConnectConstants.Errors.UnsupportedGrantType,
ErrorDescription = "The specified grant type is not supported."
});
}
}
}
I don't know if it's including anything from Project B since it's pretty basic/bare and relies on the API for everything.
I know this is a loaded and complicated question, and I'm sure I'm not presenting it as fluidly as possible so I apologize in advance for that, like I said before, I'm confused. Thank you!
Now the part I cannot wrap my brain around, how do I use Facebook login between these two totally separate projects? I know how to use it if everything is under one roof, and it's really easy, but this scenario has me totally confused since everything is separated. So for starters, who handles the 'ExternalLogin', 'ExternalLoginCallBack' logic (from .NET web template using individual accounts), the API? The HTML project?
In the recommended case (i.e when using an interactive flow like the authorization code flow or the implicit flow), the authorization server project itself is responsible of handling the external authentication dance, using the social providers you've configured in your ASP.NET Core pipeline.
In theory, the final client application (i.e the JS app) doesn't even know that you've decided to use external authentication at the authorization server level, since it's not directly linked to Facebook or Google.
In this case, the redirect_uri configured in the Facebook options must correspond to an endpoint owned by the authorization server application (in your case, it's provided by the Facebook authentication middleware).
If you don't like this approach, there's also a different flow named "assertion grant", that basically reverses how things are handled: the final client app (the JS app in your case) is directly linked to Facebook - so the redirect_uri must correspond to the JS app - and uses OpenIddict's token endpoint to "exchange" Facebook tokens with tokens issued by your own server, that can be used with your own APIs.
For more information about this flow, please read Exchanging a google idToken for local openId token c#.