Protected web calls when token is not valid - jwt

I use .NET Core 3
I've downloaded Microsoft.Identity.Web from https://github.com/Azure-Samples/active-directory-aspnetcore-webapp-openidconnect-v2/tree/aspnetcore3
I use Azure AD to get access to my protected web API. Recently I've switch to Core 3.0 where the issue occures (On 2.2 it worked fine).
Currently when I try to call web api with invalid token I get into
JwtBearerMiddlewareDiagnostics class method
private static async Task OnAuthenticationFailedAsync(AuthenticationFailedContext context)
{
Debug.WriteLine($"99. Begin {nameof(OnAuthenticationFailedAsync)}");
// Place a breakpoint here and examine context.Exception
await s_onAuthenticationFailed(context).ConfigureAwait(false);
Debug.WriteLine($"99. End - {nameof(OnAuthenticationFailedAsync)}");
}
which is absolutely right, since the token is invalid. But after that my protected controller methods calls anyway (I call them from postman by add header with Bearer and the token).
Here's my controller:
[Route("api/Points")]
[ApiController]
[Authorize(AuthenticationSchemes = "AzureAD")]
public class InstallationPointController : ControllerBase
{
...
}
Setup for AD authorization in Startup.cs:
services.AddProtectedWebApi(Configuration,subscribeToJwtBearerMiddlewareDiagnosticsEvents:true)
.AddProtectedApiCallsWebApis(Configuration, new []{ "user.read", "offline_access" }).AddInMemoryTokenCaches();
Update
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}

The order of your middleware is wrong. Routing needs to be before authentication and authorisation.
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});

One observation, I noticed that you are using the scheme value AzureAD. Currently, Microsoft.Identity.Web uses OpenIdConnectDefaults.AuthenticationScheme as a default scheme (which has OpenIdConnect as its value).
If you want to use a different scheme name with Microsoft.Identity.Web, you can use the following method:
services.AddAuthentication("MyScheme")
.AddSignIn(Configuration);

Related

WebAPI returning 401 instead of 404

...
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment() || app.Environment.EnvironmentName.ToLower() == "integration")
{
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "The API");
c.DocumentTitle = "Swagger: The API";
c.OAuthClientId("the_client");
});
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
Have an HTTP API up and running locally with Swagger turned on.
When I go to a non-existant URL I get 401 instead of 404.
I guess this is something to do with the order of the middleware, but I've guessed a number of permutations but nothing changes.
Any ideas?

SSO with openiddict

When a user logs in from site https://www.siteA.com,
an authentication cookie is recorded.
I want to read this authentication cookie from site https://www.siteB.com using User.Identity.Name.
How should I configure Program.cs(ASP.NET CORE 6.0) of Site https://www.siteA.com and Site https://www.siteB.com ?
using AuthorizationServer.Models;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<DbContext>(options =>
{
options.UseInMemoryDatabase(nameof(DbContext));
options.UseOpenIddict();
});
builder.Services.AddOpenIddict()
.AddCore(options =>
{
options.UseEntityFrameworkCore()
.UseDbContext<DbContext>();
})
.AddServer(options =>
{
options
.AllowClientCredentialsFlow();
options
.SetTokenEndpointUris("/connect/token");
options
.AddEphemeralEncryptionKey()
.AddEphemeralSigningKey();
options.RegisterScopes("api");
options
.UseAspNetCore()
.EnableTokenEndpointPassthrough();
});
builder.Services.AddHostedService<TestData>();
builder.Services.AddControllersWithViews();
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
options.LoginPath = "/account/login";
});
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
});
app.Run();
you can use ASP.Net Core Data Protection Keys configuring in your client Applications (i.e SiteA and SiteB).
Have a look at this.
https://github.com/openiddict/openiddict-core/issues/1109
and this
https://learn.microsoft.com/en-us/aspnet/core/security/data-protection/configuration/overview?view=aspnetcore-3.1
hope this helps

protect asp.net web api 2 project with identity server 4

I have an asp.net web api 2 project with .Net framework 4.8 and a centralized Identity Server 4 project. I want to validate jwt/access token generated from IS4 in my web api 2 project. I can understand its a duplicate question but somehow I am unable to find any suitable help and I am not sure what's missing. I have used IdentityServer3.AccessTokenValidation for token validation in web api project.
Startup.cs
using IdentityServer3.AccessTokenValidation;
using Microsoft.Owin;
using Owin;
[assembly: OwinStartup(typeof(WebApplicationApiNew.Startup))]
namespace WebApplicationApiNew
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions
{
Authority = "https://localhost:44373",
RequiredScopes = new[] { "api1" },
});
}
}
}
Calling this API with a valid JWT bearer token still gives 401:
[Authorize]
[HttpPost]
public String GetName1()
{
if (User.Identity.IsAuthenticated)
{
var identity = User.Identity as ClaimsIdentity;
if (identity != null)
{
IEnumerable<Claim> claims = identity.Claims;
}
return "Valid";
}
else
{
return "Invalid";
}
}
Error details:
2021-07-24 20:41:25.4133|DEBUG|Microsoft.Owin.Security.OAuth.OAuthBearerAuthenticationMiddleware|Authentication failed
Microsoft.IdentityModel.Tokens.SecurityTokenInvalidAudienceException: IDX10214: Audience validation failed. Audiences: '[PII is hidden. For more details, see https://aka.ms/IdentityModel/PII.]'. Did not match: validationParameters.ValidAudience: '[PII is hidden. For more details, see https://aka.ms/IdentityModel/PII.]' or validationParameters.ValidAudiences: '[PII is hidden. For more details, see https://aka.ms/IdentityModel/PII.]'.
at Microsoft.IdentityModel.Tokens.Validators.ValidateAudience(IEnumerable`1 audiences, SecurityToken securityToken, TokenValidationParameters validationParameters)
at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateAudience(IEnumerable`1 audiences, JwtSecurityToken jwtToken, TokenValidationParameters validationParameters)
at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateTokenPayload(JwtSecurityToken jwtToken, TokenValidationParameters validationParameters)
at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateToken(String token, TokenValidationParameters validationParameters, SecurityToken& validatedToken)
at Microsoft.Owin.Security.Jwt.JwtFormat.Unprotect(String protectedText)
at Microsoft.Owin.Security.OAuth.OAuthBearerAuthenticationHandler.<AuthenticateCoreAsync>d__3.MoveNext()
The error shows:
IDX10214: Audience validation failed
If your JWT token contains an "aud" claim, the authentication middleware
is attempting to validate the audience claim, but there is no audience specified in your options. Can you try one of the following:
Set an audience in the options:
Audience = "[your audience]"
Disable audience validation in the options:
TokenValidationParameters.ValidateAudience = false;
Refer to the identity server documentation for more details:
https://docs.identityserver.io/en/latest/topics/apis.html
I found solution to my problem. IS3 requires aud claim (with /resources in url) to be present in jwt/access token in order to validate the token. But on the other side IS4 has stopped emitting the aud claim by default. So we need to explicitly set IS4 server to emit aud claim to support legacy/old access token validation with IdentityServer3.AccessTokenValidation.
services.AddIdentityServer(options =>
{
...
options.EmitStaticAudienceClaim = true; // <- for older access token validation
})
.AddDeveloperSigningCredential()
.AddInMemoryPersistedGrants()
.AddInMemoryIdentityResources(Config.GetIdentityResources())
...

Integration AspNet.Security.OpenId.Providers Steam authorization with reactive.js?

I have a ASP.Net Core 2.2 Web API which uses Steam login for authentication using this package.
My authentication looks like this:
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie(options =>
{
options.LoginPath = "/Api/Login";
options.LogoutPath = "/Api/Logout";
})
.AddSteam(opt => { opt.CallbackPath = "/Home/SteamCallback"; opt.ApplicationKey = "XXXX"; });
I'm using this API with my react app and I want to login so I added this in my react project to login
Via Steam
And /api/login redirects user back to react's homepage:
[HttpGet("/api/login")]
public IActionResult Login(string provider="Steam")
{
return Challenge(new AuthenticationProperties { RedirectUri = "http://localhost:3000/" }, provider);
}
I know it's so stupid to try authorize like that but i dont have any idea how.
Also they say using JWT is not safe in here so I had to use cookies but could not handle how to pass logged data to react and fetch data successfully.

angular 2 login with spring security

im trying to integrate spring security with a custom angular 2 login, that is a specific endpoint of my app is protected with spring security, trying to access it will redirect to /login that is handled in angular 2. as things stands now i have no clue as to how to perform the login and grant access to the backend API once logged.
i am configuring spring security as follows:
#Override
protected void configure(final HttpSecurity http) throws Exception {
http
.csrf().disable()
.cors().and()
.authorizeRequests()
.antMatchers("/api/someEndpoint/**")
.hasRole(ADMIN_ROLE).and().formLogin()
.loginPage("/login").and().logout();
}
#Override
protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
}
as I had the default login everything worked fine, but I have found myself unable to create a working angular 2 login integration.
I tried the following code in angular 2 to no avail:
login(loginDetails:Object) {
console.log(loginDetails)
const headers = new Headers({ 'Content-Type': 'application/json' });
const options = new RequestOptions({ headers: headers });
const body = JSON.stringify(loginDetails);
console.log(headers);
console.log(body);
return this.http.post(this.loginUrl, body, options)
}
as far as I know spring security defaults for username and password variable names are "username" and "password", which i am sure are being passed in the request body so when passing some invalid user data like {"username":"admin", "password" : "pass"}I should be redirected to /login?error or something, and when successfully authenticated I should be redirected to /welcome and stay authenticated
I have the user and pass defined in my db and my custom userDetailsService checks against it
any answers, comments or questions are welcome
Once you're working with an API you've to use the HTTP Basic authentication.
It's also required to use HTTPS to prevent the main-in-middle attack.
To implement HTTP Basic with Angular the login service would look like this:
login (loginDetails: any): Observable<LoginResponse> { // custom class, may be empty for now
let headers = new Headers({
'Authorization': 'Basic ' + btoa(loginDetails.login + ':' + loginDetails.pass),
'X-Requested-With': 'XMLHttpRequest' // to suppress 401 browser popup
});
let options = new RequestOptions({
headers: headers
});
return this.http.post(this.loginUrl, {}, options)
.catch(e => this.handleError(e)); // handle 401 error - bad credentials
}
... then you subscribe this in the caller component:
loginNow() {
this
.loginService
.login(this.loginDetails)
.subscribe(next => {
this.router.navigateByUrl("/"); // login succeed
}, error => {
this.error = "Bad credentials"; // or extract smth from <error> object
});
}
Then you can use the loginNow() method inside component templates like (click)="loginNow().
As soon as the server will accept an authorization, JSESSIONID will be stored in your browser automatically because of Spring Security features and you won't be forced to send the credentials each time you access private resources.
Your login server method may look like this:
#PreAuthorize("hasRole('USER')")
#PostMapping("/login")
public ResponseEntity login() {
return new ResponseEntity<>(HttpStatus.OK);
}
... it would reject with 401 UNAUTHORIZED when the authorization fails or accept with 200 SUCCESS when it's not.
How to setup a server in the proper way there's a number of Spring Security demo projects present: https://github.com/spring-guides/tut-spring-security-and-angular-js
Your spring security config needs to look like this
http!!
.cors().and()
.csrf().disable()
.authorizeRequests()
.requestMatchers(object: RequestMatcher {
override fun matches(request: HttpServletRequest?): Boolean {
return CorsUtils.isCorsRequest(request)
}
}).permitAll()
.antMatchers("/api/**").authenticated()
.anyRequest().permitAll()
.and()
.formLogin().permitAll()
I had a similar issue, but I had to override the successlogout handler as mentioned here.