Azure b2c unauthorized when making api calls in .net5 web api - asp.net-core

I am facing an issue authorizing client apps (users) with azure B2C.
On the backend I have an asp.net5 web api. As for the front-end I am using angular client.
I have registered both apps in my B2c tenants. I've added API Premissions on both apps, also granted admin consents.
Now, when I run the user flow (from the azure portal) and specify the web api in the form, the token works fine, I can make api calls and I get status 200.
However, when tokens are retrieved upon the client app (angular), I get 401 unauthorized response.
My authentication Midleware is configured as follows:
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(jwtConfig =>
{
jwtConfig.Audience = Configuration["AzureAdB2C:ClientId"];
jwtConfig.Authority = $"{Configuration["AzureAdB2C:Instance"]}/tfp/{Configuration["AzureAdB2C:Domain"]}/{Configuration["AzureAdB2C:SignUpSignInPolicyId"]}/v2.0";
jwtConfig.RequireHttpsMetadata = false;
jwtConfig.SaveToken = true;
jwtConfig.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidAudience = jwtConfig.Audience,
ValidIssuer = $"{Configuration["AzureAdB2C:Instance"]}/{Configuration["AzureAdB2C:TenantId"]}/v2.0/"
};
});
Anyone knows what could the problem be?

Solution:
I made some research, and altered the code a little bit, to get more information on what is happening, so I found out that the problem was at the scopes. I was specifying wrong scope name at the client app, therefore I was getting 401 unauthorized.

Related

How to set custom token provider for cookies in ASP.NET Core Identity 3.1?

tl;dr: Is there way how to set custom TokenProvider or something similar e.g. (jwt tokens has IssuerSigningKey) to cookies?
I have a .Net Core 3.1 backend with identity Authentication. I had a problem with confirming generated email token.. “Invalid Token” error. From this SO answer I found out there can be problem with my hosting. I have shared hosting for my application and my application was often restarted. I believe this was causing my problems. So as answer suggested I created my own TokenProvider:
services.AddIdentity<AppUser, AppRole>()
.AddEntityFrameworkStores<MyContext>()
.AddDefaultTokenProviders()
.AddTokenProvider<AesDataProtectorTokenProvider<AppUser>>(TokenOptions.DefaultProvider);
This helps. For authentication I was using JwtBearer tokens with custom IssuerSigningKey
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(x =>
{
x.RequireHttpsMetadata = false;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false
};
})
I found out that SignInManager from Identity is creating Cookies by default. So I tough it will be better to use this cookies instead of my jwt tokens saved in localstorage. So I set up expiration time for my cookies to 30 days.
services.ConfigureApplicationCookie(options =>
{
// Cookie settings
options.ExpireTimeSpan = TimeSpan.FromDays(30);
});
Everything worked great.. until I hosted my application to server. I believe that when my app is restarted, cookie validation fail and I am getting 401 from server. So..finally to my question.. Is there way how to set custom TokenProvider or something similar e.g. (jwt tokens has IssuerSigningKey) to cookies?
There are no existing API (class, method) for you customize cookies in ASP.NET Core Identity.
You can build yourself without ASP.NET Core Identity: https://learn.microsoft.com/en-us/aspnet/core/security/authentication/cookie?view=aspnetcore-3.1
Reference: https://github.com/dotnet/aspnetcore/blob/master/src/Http/Http.Abstractions/src/CookieBuilder.cs
https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.http.cookiebuilder?view=aspnetcore-3.0
https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.http.cookieoptions?view=aspnetcore-3.0

Identity Server 4 token expiration Api vs ClientApp

i'm currently building a WebApp with authentication/authorization to access it and also to access several WebAPI's, all pointing to a Identity Server 4 host.
I have followed the official documentation of IdentityServer4 and its demos and for client authentications, token generations, user logging in, API's being called succesfully with tokens, all work fine, apparently, but recently i noticed that after some time of inactivity, the call to the API's start to receive 401 but the client application is still up with the same token.
It's like this:
Launch browser with debugging
Login with some user
Go to a view that calls one API to retrieve data for it
Keep navigating and testing, and everything else works fine
Now, the problem (after the previous step 4)
Stop debugging but keeping the browser up and running (keeping the cookies)
Changing code, implementing new stuff (basically passing some time)
Launch debug again
Using the same sessions/cookie on the already open browser, trying to navigate on the application works fine and does not required new login
Navigating to a view that will call the API using the current token, gives me the 401 when previously didnt
What i found out is that the token is expired, Visual Studio output points that out (also checking the token on https://jwt.io/ i can confirm the datetime).
Why the same token works fine for the ClientApp while invoking the API doesn't? Do i require to manually generate a new token because of the API's calls?
The configurations i'm using are:
---CLIENT application---
new Client
{
ClientId = "idWebApp",
ClientSecrets = new List<Secret> { new Secret("secret".Sha256()) },
AllowedGrantTypes = GrantTypes.Hybrid,
AllowAccessTokensViaBrowser = false,
EnableLocalLogin = true,
RedirectUris = { "http://localhost:5901/signin-oidc" },
FrontChannelLogoutUri = "http://localhost:5901/signout-oidc",
PostLogoutRedirectUris = { "http://localhost:5901/signout-callback-oidc" },
AllowOfflineAccess = true,
AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.OfflineAccess,
"apiAccess",
},
RequireConsent = false,
}
---API resource---
(Just using simple ctor to initialize with a 'Name')
new ApiResource("apiAccess")
---Custom Claims---
new IdentityResource()
{
Name = "appCustomClaims",
UserClaims = new List<string>()
{
"customRole"
}
}
---Startup code of ClientApp---
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
options.Authority = "http://localhost:5900";
options.RequireHttpsMetadata = false;
options.ClientId = "idWebApp";
options.ClientSecret = "secret";
options.ResponseType = "code id_token";
options.Scope.Add("profile");
options.Scope.Add("offline_access");
options.ClaimActions.MapUniqueJsonKey("offline_access", "offline_access");
options.Scope.Add("appCustomClaims");
options.ClaimActions.MapJsonKey("customRole", "customRole");
options.Scope.Add("apiAccess");
options.GetClaimsFromUserInfoEndpoint = true;
options.SaveTokens = true;
options.TokenValidationParameters.RoleClaimType = "customRole";
});
Why the same token works fine for the ClientApp while invoking the API
doesn't?
Two things:
The expiration time of the access token is unrelated to your actions.
Once issued a JWT token can't be changed. By default the token expires after 3600 seconds.
The difference between the application and the api: the application uses cookies, the api a bearer token.
The cookie has its own expiration logic. This means that it expires at a different time, unrelated to the expiration time of the access token, and also can be kept alive because the cookie can be updated, unlike the JWT access token.
For offline_access you require to obtain a new access token, using the refresh token. As explained here.

Generate JWT token on successful authentication with Windows Authentication

I'm having some difficulty understanding how to use Windows Authentication to authenticate a user, but then return a JWT to the client containing that authenticated user's claims. This is using .Net Core 2.0.
I've put the following in Startup.cs.
services.AddAuthentication
(
IISDefaults.AuthenticationScheme
).AddJwtBearer("Bearer", options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false,
ValidateIssuer = false,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("someKey")),
ValidateLifetime = true,
};
}
);
However, it doesn't seem that this is sufficient to generate the token. Some other guidance I've read suggests the token should be generate manually as part of an authorization routine but this seems to be tailored for when validating a username/password against a database or other provider. (For instance: https://fullstackmark.com/post/13/jwt-authentication-with-aspnet-core-2-web-api-angular-5-net-core-identity-and-facebook-login.) But with windows auth there does not appear to be an event or other structure that allows me to do this.
Any ideas?

.net Core Api authentication with ADFS 2012

I need to configure my .Net Core Web Api (.Net Framework) to use ADFS 3.0 (2012) to validate the Bearer tokens sent by our mobile clients.
I am able to generate the access_token from the ADFS server, and I pass it in the Authorization header.
My problem is in the API: how do I configure it to validate and autorize the user?
I searched in many places and I could not find a definitive method of doing it.
What I tried so far:
Used IdentityServer4 (Failed because it uses JWT and ADFS doesn't offer OpenID
Tried UseOpenIdConnectAuthentication (found example at IdentityServer4)
a custom Middleware
I can't use another method, I need to support oAuth2.
So, how do I do it?
Here is my latest try:
var connectOptions = new OpenIdConnectOptions
{
AuthenticationScheme = "adfs",
SignInScheme = "idsrv.external", //IdentityServerConstants.ExternalCookieAuthenticationScheme,
SignOutScheme = "idsrv", //IdentityServerConstants.SignoutScheme,
AutomaticChallenge = false,
DisplayName = "ADFS",
Authority = $"https://{options.AdfsHostName}/adfs/oauth2",
ClientId = options.ClientID,
ResponseType = "id_token",
Scope = { "openid profile" },
CallbackPath = new PathString("/signin-adfs"),
SignedOutCallbackPath = new PathString("/signout-callback-adfs"),
RemoteSignOutPath = new PathString("/signout-adfs"),
ClaimsIssuer = $"https://{options.AdfsHostName}/adfs/services/trust",
//TokenValidationParameters = new TokenValidationParameters
//{
// ValidateIssuer = true,
// ValidIssuer = $"https://{options.AdfsHostName}/adfs/services/trust"
//},
};
app.UseOpenIdConnectAuthentication(connectOptions);
I get a very quick 401 on every calls, with a valid token. In fact, while I see the connection in the console window, I don't see any other log in the Roslyn console window regarding the security validation.
I'm currently using ASP.Net Core 1.1.X, and if I can I'd avoid moving to .Net Core 2.0, as we are late in the project and it contains many breaking changes...
Feel free to ask for more info, and I'll appreciate all the good advices!
As it turns out, we can use the JwtBearerAuthentication with ADFS 3.0.
My initial problem with it was that it went to fetch the metadata at /.well-known/openid-configuration, but ADFS 3.0 does not support OpenID and this returns a 404.
I read in another post (I'll update it when I find it) that if with the right configuration, it won't need to fetch the config. But what configuration?
Well I found deep in the (MS) code that if one pass an OpenIdConnectConfiguration object to the Configuration property of the JwtBearerOptions, it wont fetch the metadata.
So here is my code now:
var rawCertData = Convert.FromBase64String(options.X509SigninCertificate);
X509Certificate2 cert = new X509Certificate2(rawCertData);
SecurityKey signingKey = new X509SecurityKey(cert);
The X509 cert data comes from the supported adfs metadata at this url
https://Your.ADFS.Site/FederationMetadata/2007-06/FederationMetadata.xml
It contains this:
<KeyDescriptor use="signing">
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
<X509Data>
<X509Certificate>SOMEUUENCDODEDSTRING=</X509Certificate>
</X509Data>
</KeyInfo>
</KeyDescriptor>
I simply copied the UUEncoded string in my settings' X509SigninCertificate property.
var tokenValidationParameters = new TokenValidationParameters
{
// The signing key must match!
ValidateIssuerSigningKey = true,
IssuerSigningKey = signingKey,
// Validate the JWT Issuer (iss) claim
ValidateIssuer = true,
ValidIssuer = $"https://{options.AdfsHostName}/adfs/services/trust",
// Validate the JWT Audience (aud) claim
ValidateAudience = true,
ValidAudience = options.ClientUri, //"https://YOUR-AUDIENCE/",
// Validate the token expiry
ValidateLifetime = true,
// If you want to allow a certain amount of clock drift, set that here:
ClockSkew = TimeSpan.Zero
};
var connectOptions = new OpenIdConnectConfiguration
{
Issuer = $"https://{options.AdfsHostName}/adfs/services/trust",
};
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
TokenValidationParameters = tokenValidationParameters,
Configuration = connectOptions
});
The important line here is
Configuration = connectOptions
By doing this, you tell the validator to not fetch the metadata. Simple as that.
I was able to validate my token (AUD, ISS and SIGN) and I can use ADFS in my project.
Only ADFS 2016 supports OpenID Connect. If you want to use the OAuth endpoint in 2012, you need to write your own authorisation handler. An example to build on would be ASP.NET Core's own Twitter implementation. Note that these handlers need to be implemented differently in ASP.NET Core 1.* vs 2.0+.

IdentityServer4 Authorize always gets "The signature key was not found" on Azure AppService

I have an IdentityServer4 app based on the IS4 Identity sample, and an API using bearer tokens for it's Authorization via IS4.AccessTokenValidation. This is working fine on localhost via VisualStudio, and when I deploy to a Windows 2012 VM and hosted via IIS. When I deploy the Identity server to Azure as an App Service website, all is fine too. However when the API is deployed as an App Service using same domain and certificate as the VM, any method with an Authorize attribute (with a policy or none it doesn't matter) always returns a 401 with the header message:
Www-Authenticate: Bearer error="invalid_token", error_description="The signature key was not found"
We're using .NET 4.5.2, with the latest releases of IdentityServer4, and IdentityServer4.AccessTokenValidation packages. I've also pulled the latest of these packages from GitHub from 30/08/16 with no change. I don't think it's a bug is IS4 Validator anyway, but I don't know what might cause this. Any suggestions? Is it an Azure host bug?
I'd love to be able to debug this, but I can't get Remote Debug working to this app even when I rebuilt from scratch, and app logs tell me nothing. I've had a rummage in the ASP.NET Security repo, but without more logging or debug access, I'm pretty clueless how to fix this problem.
API Configure is very basic:
var jwtBearerOptions = new JwtBearerOptions()
{
Authority = Configuration["Authentication:IdentityServer:Server"],
Audience = Configuration["Authentication:IdentityServer:Server"]+"/resources",
RequireHttpsMetadata = false,
AutomaticAuthenticate = true,
AutomaticChallenge = true,
};
app.UseJwtBearerAuthentication(jwtBearerOptions);
and the Identity Server is straight out of the samples, and using a purchased certificate for signing.
Has anyone else got this configuration fully working as 2 Azure App Services? Or what might possibly cause this error given the same bearer token sent to the VM hosted API is acceptable.
It turned out you need to explicitly set the IssuerSigningKey in TokenValidationParameters. So I get the certificate from the App Service store, and add it via JwtBearerOptions.TokenValidationParameters. So Startup config looks like this:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
...
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap = new Dictionary<string, string>();
var tokenValidationParameters = new TokenValidationParameters
{
// The signing key must match!
ValidateIssuerSigningKey = true,
IssuerSigningKey = new X509SecurityKey(GetSigningCertificate()),
// Validate the JWT Issuer (iss) claim
ValidateIssuer = false,
//ValidIssuer = "local",
// Validate the JWT Audience (aud) claim
ValidateAudience = false,
//ValidAudience = "ExampleAudience",
// Validate the token expiry
ValidateLifetime = true,
// If you want to allow a certain amount of clock drift, set that here:
ClockSkew = TimeSpan.Zero
};
var jwtBearerOptions = new JwtBearerOptions()
{
Authority = Configuration["Authentication:IdentityServer:Server"],
Audience = Configuration["Authentication:IdentityServer:Server"]+"/resources",
RequireHttpsMetadata = false,
AutomaticAuthenticate = true,
AutomaticChallenge = true,
TokenValidationParameters = tokenValidationParameters
};
app.UseJwtBearerAuthentication(jwtBearerOptions);
app.UseMvc();
...
}
No idea why this is only needed on the Azure App Service and not on a server or development machine. Can anyone else explain it? It would suggest ValidateIssuerSigningKey default to true for App Service and false anywhere else.