Auth0 + Swashbuckle .Net Core 2.2. Missing claims in jwt token when using SwaggerUI - asp.net-core

I am making a ASP.Net Core WebApi which is authentication via Auth0. I am using Swagger and SwaggerUI and trying to authenticate from Swagger UI.
// Add authentication services
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultSignInScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect("Auth0", options =>
{
// Set the authority to your Auth0 domain
options.Authority = $"https://{Configuration["Auth0:Authority"]}";
// Configure the Auth0 Client ID and Client Secret
options.ClientId = Configuration["Auth0:ClientId"];
options.ClientSecret = Configuration["Auth0:ClientSecret"];
// Set response type to code
options.ResponseType = "code";
// Configure the scope
options.Scope.Clear();
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("offline_access");
options.SaveTokens = true;
// Set the callback path, so Auth0 will call back to http://localhost:3000/callback
// Also ensure that you have added the URL as an Allowed Callback URL in your Auth0 dashboard
options.CallbackPath = new PathString("/callback");
// Configure the Claims Issuer to be Auth0
options.ClaimsIssuer = "Auth0";
// Saves tokens to the AuthenticationProperties
options.SaveTokens = true;
options.Events = new OpenIdConnectEvents
{
OnRedirectToIdentityProvider = context =>
{
context.ProtocolMessage.SetParameter("audience", #"https://predictor-dev.api");
return Task.FromResult(0);
},
// handle the logout redirection
OnRedirectToIdentityProviderForSignOut = (context) =>
{
var logoutUri = $"https://{Configuration["Auth0:Authority"]}/v2/logout?client_id={Configuration["Auth0:ClientId"]}";
var postLogoutUri = context.Properties.RedirectUri;
if (!string.IsNullOrEmpty(postLogoutUri))
{
if (postLogoutUri.StartsWith("/"))
{
// transform to absolute
var request = context.Request;
postLogoutUri = request.Scheme + "://" + request.Host + request.PathBase + postLogoutUri;
}
logoutUri += $"&returnTo={ Uri.EscapeDataString(postLogoutUri)}";
}
context.Response.Redirect(logoutUri);
context.HandleResponse();
return Task.CompletedTask;
}
};
})
.AddJwtBearer(options =>
{
options.Authority = Configuration["Auth0:Authority"];
options.Audience = Configuration["Auth0:Audience"];
options.TokenValidationParameters = new TokenValidationParameters
{
RoleClaimType = "http://schemas.microsoft.com/ws/2008/06/identity/claims/roles"
};
options.ClaimsIssuer = "Auth0";
});
services.AddCors(options =>
{
options.AddPolicy("AllowSpecificOrigin",
builder =>
{
builder
.WithOrigins(Configuration["FrontendBaseUrl"])
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials();
});
});
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info { Title = "Predictor API", Version = "v1" });
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
c.IncludeXmlComments(xmlPath);
c.AddSecurityDefinition("oauth2", new OAuth2Scheme
{
Type = "oauth2",
Flow = "implicit",
AuthorizationUrl = $"{Configuration["Auth0:Authority"]}authorize?audience={Configuration["Auth0:Audience"]}",
Scopes = new Dictionary<string, string>
{
{ "read:books", "Access read book operations" },
{ "write:books", "Access write book operations" }
}
});
c.OperationFilter<SecurityRequirementsOperationFilter>();
});
Here is the token which is returned after authentication via SwaggerUI:
{
"iss": "my iss",
"sub": "my sub",
"aud": "my aud",
"iat": 1556002815,
"exp": 1556010015,
"azp": "azp",
"scope": "read:books"
}
The problem here is that token doesn't have openid and profile information.
I don't have any custom rules in Auth0 that could limit my scopes (I removed them totally).I tried different options, but I could not get any additional claims.
Is there any configuration in Swagger that I am missing?
Thank you.

You have to pass "openid" and "profile" scopes to extend your token with openid and profile information

Related

.NET 5 Blazor Server OKTA Authentication showing HTTP Error 400

Using ASP.NET Core (.NET 5) Blazor Server with OKTA. OKTA log page has been prompted. I am getting below error messge on submitting OKTA uid/pwd
HTTP Error 400. The size of the request headers is too long.
My middleware is as like below, using an OpenId Connect.
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddOpenIdConnect(options =>
{
options.RemoteAuthenticationTimeout = TimeSpan.FromMinutes(30);
options.AuthenticationMethod = OpenIdConnectRedirectBehavior.RedirectGet;
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.Authority = configuration["Okta:Domain"] + "/oauth2/default";
options.RequireHttpsMetadata = true;
options.ClientId = configuration["Okta:ClientId"];
options.ClientSecret = configuration["Okta:ClientSecret"];
options.ResponseMode = OpenIdConnectResponseMode.FormPost;
options.ResponseType = OpenIdConnectResponseType.Code;
options.Scope.Add("offline_access");
options.UseTokenLifetime = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.AccessDeniedPath = "/Public/AccessDenied";
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("email");
// Describe how to map the user info we receive to user claims
options.ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "sub", "string");
options.ClaimActions.MapJsonKey(ClaimTypes.GivenName, "given_name", "string");
options.ClaimActions.MapJsonKey(ClaimTypes.Name, "given_name", "string");
options.ClaimActions.MapJsonKey("LastName", "lastname", "string");
options.ClaimActions.MapJsonKey("FirstName", "firstname", "string");
options.ClaimActions.MapJsonKey(ClaimTypes.Email, "email", "string");
options.ClaimActions.MapJsonKey("Groups", "Groups", "string");
options.ClaimActions.MapJsonKey("membership_roles", "membership_roles", "string");
options.SaveTokens = true;
options.NonceCookie.SameSite = SameSiteMode.Unspecified;
options.CorrelationCookie.SameSite = SameSiteMode.Unspecified;
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
RoleClaimType = "groups",
RequireSignedTokens = true,
ValidateIssuer = false
};
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, displayName: $"EPDOne_{GlobalVariables.LocalEnv.EnvironmentName}",
options =>
{
options.Cookie.Name = $"EPDOne_{ GlobalVariables.LocalEnv.EnvironmentName}";
options.Cookie.HttpOnly = false;
options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
options.Cookie.IsEssential = true;
options.Events = new CookieAuthenticationEvents
{
// this event is fired everytime the cookie has been validated by the cookie middleware,
// so basically during every authenticated request
// the decryption of the cookie has already happened so we have access to the user claims
// and cookie properties - expiration, etc..
OnValidatePrincipal = context =>
{
// since our cookie lifetime is based on the access token one,
// check if we're more than halfway of the cookie lifetime
var now = DateTimeOffset.UtcNow;
TimeSpan timeElapsed = now.Subtract(DateTime.Now.AddDays(1));
TimeSpan timeRemaining = now.Subtract(DateTime.Now.AddDays(2));
if (context is not null)
{
if (context.Properties is not null && context.Properties.IssuedUtc is not null)
{
timeElapsed = now.Subtract(context.Properties.IssuedUtc.Value);
}
else
{
context.ShouldRenew = true;
}
if (context.Properties is not null && context.Properties.ExpiresUtc is not null)
{
timeRemaining = context.Properties.ExpiresUtc.Value.Subtract(now);
}
else
{
context.ShouldRenew = true;
}
}
if (timeElapsed > timeRemaining || context?.ShouldRenew == true)
{
context.ShouldRenew = true;
var identity = (ClaimsIdentity)context?.Principal?.Identity;
if (identity is not null && identity.IsAuthenticated)
{
string CurrentBaseAddress = CurrentURL(context.HttpContext);
string returnUrl = "";
if (string.IsNullOrEmpty(CurrentBaseAddress) == false)
{
returnUrl = "?returnUrl=" + CurrentBaseAddress;
}
context.Response.Redirect(GlobalVariables.OKTACallBackURI + $"/refresh{returnUrl}");
}
}
return Task.CompletedTask;
}
};
});
services.AddRazorPages(options =>
{
options.Conventions.AuthorizeFolder("/");
//options.Conventions.AllowAnonymousToFolder("/Public");
}
);
As you can see above, I used OpenId in Startup.cs and application is prompting with OKTA credential dialog and after submitting uid/pwd, page behaves like in a loop and then shows the HTTP Error 400 message. Any clues here?
Philipp Grigoryev - Thanks for your time. I later noticed inside my .NET Core Startup.cs file below code.
app.UseAuthorization();
app.UseAuthorization();
The correct lines should be
app.UseAuthentication();
app.UseAuthorization();
So actually Authentication middleware itself not enabled and in hurry I was using 2 lines of enabling Authorization itself. After the mentioned correction, it works. Sorry to anyone who spent time on this. I am closing this query.

Validate the JWT Bearer Token In Identity Server 4

I have an Identity Server running based on IdentityServer 4, and I have an ASP.NET WebAPI built in ASP.Net Core Web API. I have a successfully login on the /connect/token endpoint of the identity server. I want to check the validity of JWT bearer token sent in the header of my API requests.
This is the configuration in my startup API project :
In ConfigureServices :
services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(options =>
{
//base-address of my identityserver
options.Authority = "https://localhost:5000/";
//name of the API resource
options.ApiName = "API_Resource_Name";
});
In Configure :
app.UseAuthentication();
NB : I've Added Authorize Annotation to my controller
Add authentication and authorization to your API Startup.cs ConfigureServices:
services.AddAuthentication("bearer")
.AddJwtBearer("bearer", options =>
{
options.Authority = Configuration["Authority"];
options.Events = new Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) && (path.StartsWithSegments("/chathub")))
{
context.Token = accessToken;
}
return Task.CompletedTask;
},
OnTokenValidated = context =>
{
var token = context.SecurityToken as JwtSecurityToken;
if (token != null)
{
ClaimsIdentity identity = context.Principal.Identity as ClaimsIdentity;
if (identity != null)
{
identity.AddClaim(new Claim("access_token", token.RawData));
}
}
return Task.CompletedTask;
}
};
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false,
NameClaimType = "name",
RoleClaimType = "role"
};
});
And then...
services.AddAuthorization(options =>
{
options.AddPolicy("ApiScope", policy =>
{
policy.RequireAuthenticatedUser();
policy.RequireClaim("scope", "SignalR.API");
});
});
Inside Configure...
app.UseAuthentication();
app.UseAuthorization();

SignalR authentication with Blazor wasm Bearer Token

I authenticated to Signalr with Bearer token and signalr said Authentication was
successful.
But Signalr Hub Context.Identity.User.Name is null after authentication.
How can i access to authenticated user information in SignalR Hub for connectionid and user mapping.
My Startup.cs code for authentication.
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
{
options.Authority = "https://security.minima.local";
options.Audience = "blazor_web_app_api";
options.RequireHttpsMetadata = false;
options.TokenValidationParameters = new
TokenValidationParameters()
{
ValidateAudience = false
};
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) &&
(path.StartsWithSegments("/notification")))
{
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
})
.AddIdentityServerJwt();
You need to tell the TokenValidator which claim is the name and role claim and you do that by setting:
.AddMyJwtBearer(opt =>
{
...
opt.TokenValidationParameters.RoleClaimType = "roles";
opt.TokenValidationParameters.NameClaimType = "name";

Audience parameter is missing from Bearer Token while using Swagger (swashbuckle v5.3.1) with Asp.net Core 3.1.1

Jwt Authentication section from Startup.cs:
services.AddAuthentication(options =>
{
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.Authority = config["Authority"];
options.RequireHttpsMetadata = Convert.ToBoolean(config["RequireHttpsMetadata"]);
options.Authority = config["Authority"];
options.Audience = config["Audience"];
options.TokenValidationParameters = new TokenValidationParameters
{
ValidAudience = config["Audience"],
ValidateAudience = Convert.ToBoolean(config["ValidateAudience"]),
ValidateIssuer = Convert.ToBoolean(config["ValidateIssuer"]),
};
});
Swagger configuration:
services.AddSwaggerGen(setup => {
setup.SwaggerDoc("1.0", new OpenApiInfo
{
Title = "Switchboard Live Cloud API v1.0",
Version = "1.0",
Description = "Programmable access to Switchboard Live's Cloud Platform.",
});
setup.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
{
Type = SecuritySchemeType.OAuth2,
Flows = new OpenApiOAuthFlows
{
Implicit = new OpenApiOAuthFlow
{
AuthorizationUrl = new System.Uri(string.Format("{0}/connect/authorize", authority)),
Scopes = new Dictionary<string, string> {
{ "read", "R" },
{ "write", "W" }
}
}
}
});
setup.AddSecurityRequirement(new OpenApiSecurityRequirement()
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "oauth2" }
},
new[] { "read", "write" }
}
});
.....
app.UseSwagger();
app.UseSwaggerUI(setup => {
setup.SwaggerEndpoint("/swagger/1.0/swagger.json", "Title");
setup.RoutePrefix = string.Empty;
});
I don't see what am I doing wrong here, but when I start the application, check the scopes and go through the authorization process, I end up with a Bearer Token that do NOT have the audience field encrypted in it, so all my requests ends up with a 401 Unauthorized response and the following header error:
www-authenticate: Bearer error="invalid_token", error_description="The audience 'empty' is invalid"
Any suggestions/solutions for this?
There was a major change in IdentityServer4 version v4 they are no longer setting the aud claim by default.
If you check the configuration section of the official documentation it says you need to disable the aud Claim:
https://identityserver4.readthedocs.io/en/latest/quickstarts/1_client_credentials.html#configuration
So, you need set the property ValidateAudience = false with something like this:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options =>
{
options.Authority = "https://localhost:5001";
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false
};
});
}

ASP.NET Authorize Filter Denies Access for User in Specified Role

In my ASP.NET Core 2.0 Application, I am stuck with an issue an Admin logged in User cannot access controller I used the Authorize Filter on [Authorize(Policy="AdminAlone")].
I confirmed that the user is in the "Administrators" role and added the policy in startup.cs but it redirects to an AccessDenied view when I try to access the controller.
I saw a similar problem on this link, but the solution didn't help me
Startup Class in MVC Client - ConfigureServices
services.AddMvc();
services.AddSession();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddAuthorization(options =>
{
options.AddPolicy("AdminAlone", policy => policy.RequireRole("Administrators"));
});
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultForbidScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddCookie("Cookies")
.AddOpenIdConnect("Bearer", options =>
{
options.SignInScheme = "Cookies";
options.Authority = "http://localhost:5000";
options.RequireHttpsMetadata = false;
options.ClientId = "mvcWeb";
options.ClientSecret = "spring12345";
options.ResponseType = OidcConstants.ResponseTypes.CodeIdToken;
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("NuB.HospitalSearch");
options.Scope.Add("offline_access");
});
Web API ConfigureServices
var jwtSecurityTokenHandler = new JwtSecurityTokenHandler
{
InboundClaimTypeMap = new Dictionary<string, string>()
};
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(option =>
{
option.Audience = "NuB.HospitalSearch";
option.Authority = "http://localhost:5000";
option.RequireHttpsMetadata = false;
option.SecurityTokenValidators.Clear();
option.SecurityTokenValidators.Add(jwtSecurityTokenHandler);
option.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
ValidateAudience = true,
ValidAudience = "NuB.HospitalSearch",
ValidateIssuer = true
};
});
You may try the following. Inside your AddOpenIdConnect configuration add
options.TokenValidationParameters = new TokenValidationParameters {
NameClaimType = JwtClaimTypes.Name,
RoleClaimType = JwtClaimTypes.Role
};
In fact, this property defines the types and definitions required for validating a token. Please refer to this post from Dominick Baier for a more detailed explanation.