JSON web Tokens and cookie authentication in the same time - json

I have a web app with cookie and JWT authentication. The site uses cookie schema, web api - JWT schema. And there is a controller, which requires both types(if request has 'Bearer' header - JWT, otherwise - cookie, but only the cookie one works. Here are ConfigureServices and Configure methods:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddOptions();
services.AddCommonLogger();
services.AddAutoMapper();
services.AddDatabase(Configuration);
services.AddLogicUnits();
services.AddFrontendLogic(Configuration);
services.ConfigureSettings(Configuration);
services.AddCommonServices();
var authTokenSettings = Configuration.GetSection(nameof(TokenProviderSettings)).Get<TokenProviderSettings>();
services.AddAuthentication()
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, u =>
{
u.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = authTokenSettings.Issuer,
ValidAudience = authTokenSettings.Audience,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(authTokenSettings.Key))
};
})
.AddCookie("CookieAuthScheme", cfg => cfg.SlidingExpiration = true);
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, UnhandledExceptionLoggerProvider provider)
{
loggerFactory.AddNLog();
loggerFactory.AddProvider(provider);
app.AddNLogWeb();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseStaticFiles();
app.UseAuthentication();
app.UseMvcWithDefaultRoute();
}
If I set AuthenticationScremes like this
[Authorize(AuthenticationSchemes = "Bearer,CookieAuthScheme")]
public async Task<IActionResult> MyRecords()
{ // Do some work...}
I get http 404, and if I use empty [Authorise] attribute a cookie authentication is used.
If I remove .AddCookie("CookieAuthScheme", cfg => cfg.SlidingExpiration = true); the JWT-based authentication is used and works fine. What am I doing wrong?

Related

.Net 6 - exception middleware not catching authorization error [duplicate]

I'm developing ASP Core Web API using dotnet core v3.1.
I'm using JWT tokens for authentication. And for authorization I use the [Authorize] attribute.
How can I create my own response if the user is not logged in (while trying to access the action marked with the [Authorize] attribute) or the user's token is not authenticated.
I came across a solution using a custom authorization attribute inherited from the default one. And in this example, the HandleUnauthorizedRequest method is overridden. But I don't see such a method inside the AuthorizeAttribute class.
Is there a way to create custom unauthorized responses with http body?
Since you are using JWT bearer authentication, one way to override the default Challenge logic (which executes to handle 401 Unauthorized concerns) is to hook a handler to the JwtBearerEvents.OnChallenge callback in Startup.ConfigureServices:
services.AddAuthentication().AddJwtBearer(options =>
{
// Other configs...
options.Events = new JwtBearerEvents
{
OnChallenge = async context =>
{
// Call this to skip the default logic and avoid using the default response
context.HandleResponse();
// Write to the response in any way you wish
context.Response.StatusCode = 401;
context.Response.Headers.Append("my-custom-header", "custom-value");
await context.Response.WriteAsync("You are not authorized! (or some other custom message)");
}
};
});
This will override the default challenge logic in JwtBearerHandler.HandleChallengeAsync, which you can find here for reference purposes.
The default logic does not write any content to response (it only sets the status code and set some headers). So to keep using the default logic and add content on top of it, you can use something like this:
options.Events = new JwtBearerEvents
{
OnChallenge = context =>
{
context.Response.OnStarting(async () =>
{
// Write to the response in any way you wish
await context.Response.WriteAsync("You are not authorized! (or some other custom message)");
});
return Task.CompletedTask;
}
};
For .net core 5 web api project with jwt authentication use this middleware in Configure method of Startup.cs for show ErrorDto in Swagger:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "LoginService v1"));
}
app.ConfigureExceptionHandler();
app.UseHttpsRedirection();
app.UseRouting();
// Unauthorized (401) MiddleWare
app.Use(async (context, next) =>
{
await next();
if (context.Response.StatusCode == (int)HttpStatusCode.Unauthorized) // 401
{
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(new ErrorDto()
{
StatusCode = 401,
Message = "Token is not valid"
}.ToString());
}
});
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
ErrorDto :
public class ErrorDto
{
public int StatusCode { get; set; }
public string Message { get; set; }
public override string ToString()
{
return JsonSerializer.Serialize(this);
}
}
This is what I came up with for responding with the same ProblemDetails you would get from returning Unauthorized() in an ApiController:
.AddJwtBearer(options =>
{
// Other configs...
options.Events = new JwtBearerEvents
{
OnChallenge = async context =>
{
// Call this to skip the default logic and avoid using the default response
context.HandleResponse();
var httpContext = context.HttpContext;
var statusCode = StatusCodes.Status401Unauthorized;
var routeData = httpContext.GetRouteData();
var actionContext = new ActionContext(httpContext, routeData, new ActionDescriptor());
var factory = httpContext.RequestServices.GetRequiredService<ProblemDetailsFactory>();
var problemDetails = factory.CreateProblemDetails(httpContext, statusCode);
var result = new ObjectResult(problemDetails) { StatusCode = statusCode };
await result.ExecuteResultAsync(actionContext);
}
};
});

.Net5 backend receives empty JSON

I implemented a simple server with .Net5 and an Angular frontend. My api works with post man but only if I set Content-Type equal to application/x-www-form-urlencoded. When I use Content-Type equal to application/json the server receives empty json. When I use my frontend I think I send to server an application/json because it receives the empty one. Below my code.
api.service.ts
public createProva(prova:Iprova): Observable<Iprova> {
return this.http.post<Iprova>( this.Url, prova );
}
When I console.log prova it return a correct Json
controller.cs
[HttpPost]
[Route("url")]
public async Task<IActionResult> postProva(provaDto s)
{
porvaModel prova = new provaModel
{
Id = s.id,
Name = s.name
};
_context.provaModel.Add(prova);
await _context.SaveChangesAsync();
return CreatedAtAction(nameof(getProva), new { id = prova.Id }, prova);
}
but when backend receives provaDto, s is empty with id equal to 0 and name equal to null.
For simplicity, I assume you have a basic setup for your controller such as this :
[ApiController]
[Route("[controller]")]
public class ProvaController : ControllerBase
I also disable the https redirection in the startup.cs for development purposes, while keeping the default routing functionality.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (!env.IsDevelopment())
{
app.UseHttpsRedirection();
}
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
I'll keep the same post method:
[HttpPost]
public async Task<IActionResult> postProva(provaDto s)
{
porvaModel prova = new provaModel
{
Id = s.id,
Name = s.name
};
_context.provaModel.Add(prova);
await _context.SaveChangesAsync();
return CreatedAtAction(nameof(getProva), new { id = prova.Id }, prova);
}
As for the Postman setup, I use a POST request on http://localhost:5000/Prova (I use the default launchSettings using dotnet CLI), I also keep default headers. In the Body, I'll use raw and select JSON format, giving it a simple payload :
{
"id": 0,
"name": "string"
}
As for the angular part, you can specify the content-type of your resquest with the httpOptions :
httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
})
};
public createProva(prova:Iprova): Observable<Iprova> {
return this.http.post<Iprova>( this.Url, prova , this.httpOptions);
}

How to fix "HttpMessageNotReadableException: Required request body is missing"?

I'm integrating Angular with Spring boot for basic CRUD application with login module.
Login module is working fine, whereas, for creation method I get: "HttpMessageNotReadableException: Required request body is missing" for that particular method.
This is for a basic login and CRUD operation app with angular 7 and spring boot.
arrNewUser: any[] = [];
url = 'http://localhost:8080/api/user-management/add';
constructor(private http: HttpClient, private router: Router) { }
private options = { headers: new HttpHeaders().set('Content-Type', 'application/json') };
createUser(form: NgForm) {
this.arrNewUser = form.value;
console.log('array', this.arrNewUser);
console.log('value', form.value);
this.http.post(this.url, JSON.stringify(this.arrNewUser), this.options).subscribe((res: Response) => {
console.log('Response:--', res.status);
console.log(this.router.url);
});
}
#RestController
#RequestMapping(path="/api", method = {RequestMethod.GET, RequestMethod.POST})
#CrossOrigin(origins="http://localhost:4200")
public class ControllerClass {
#PostMapping(path="/user-management/add", produces = { MediaType.APPLICATION_JSON_UTF8_VALUE })
public Status addUsers(#RequestBody String data) {
.......
return status;
}
I'm able to get required output thru postman and thru localhost:4200 angular port, but not from localhost:8080 i.e., spring boot address.

Auth0 and Asp.Net Core 2.0 Razor Pages LoginPath Issue

I'm creating a Razor Pages app with Auth0 as the authentication provider and I'm running into a LoginPath issue. I've seen other StackOverflow answers that say you should put this into the ConfigureServices method:
services.ConfigureApplicationCookie(options => options.LoginPath = "/Index/Login");
I tried putting that below the services.AddAuthentication section of code, but that doesn't redirect to /Index/Login. I'm not seeing anywhere else how to properly get an [Authorize] attribute failure to redirect to the Auth0 login page. I figured if I could get the path set to the Index page this code would run:
public async void OnGetLogin(string returnUrl = "/")
{
await HttpContext.ChallengeAsync("Auth0", new AuthenticationProperties() { RedirectUri = returnUrl });
}
My full ConfigureServices code is:
public void ConfigureServices(IServiceCollection services)
{
// Add authentication services
services.AddAuthentication(options => {
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect("Auth0", options => {
// Set the authority to your Auth0 domain
options.Authority = $"https://{Configuration["Auth0:Domain"]}";
// 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("groups");
options.Scope.Add("profile");
options.Scope.Add("email");
// Set the callback path, so Auth0 will call back to http://localhost:5000/signin-auth0
// Also ensure that you have added the URL as an Allowed Callback URL in your Auth0 dashboard
options.CallbackPath = new PathString("/signin-auth0");
// Configure the Claims Issuer to be Auth0
options.ClaimsIssuer = "Auth0";
options.Events = new OpenIdConnectEvents
{
// handle the logout redirection
OnRedirectToIdentityProviderForSignOut = (context) =>
{
var logoutUri = $"https://{Configuration["Auth0:Domain"]}/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;
}
};
});
services.ConfigureApplicationCookie(options => options.LoginPath = "/Index/Login");
services.AddMvc();
}
Anyone know how to do this properly in 2.0?
You need to add this snippet in order to work:
services.AddMvc()
.AddRazorPagesOptions(options =>
{
options.Conventions.AuthorizeFolder("/");
options.Conventions.AllowAnonymousToPage("/Account/Login");
});
When I added this, my code started to work and redirect to the right login page.

GetRequestToken is not working in TweetSharp on Windows Phone

I can't use GetRequestToken in TwitterService anymore
and also GetAccessToken!
TwitterService service = new TwitterService("ConsumerKey", "ConsumerKeySecret");
service.GetRequestToken(Constants.CallbackUri, (request, response) =>
{
if (response.StatusCode == HttpStatusCode.OK)
{
Request = request;
var uri = service.GetAuthorizationUri(request);
Dispatcher.BeginInvoke(() => AuthBrowser.Navigate(uri));
}
});
it gives me:
'TweetSharp.TwitterService' does not contain a definition for 'GetRequestToken' and no extension method 'GetRequestToken' accepting a first argument of type 'TweetSharp.TwitterService' could be found (are you missing a using directive or an assembly reference?)
I solved it by getting Request Token via Hammock(https://github.com/danielcrenna/hammock)
and here is the code
/// <summary>
/// Gets Twitter Request Token
/// </summary>
private void GetTwitterToken()
{
var credentials = new OAuthCredentials
{
Type = OAuthType.RequestToken,
SignatureMethod = OAuthSignatureMethod.HmacSha1,
ParameterHandling = OAuthParameterHandling.HttpAuthorizationHeader,
ConsumerKey = "Your Consumer Key",
ConsumerSecret = "Your Consumer Secret",
Version = TwitterSettings.OAuthVersion,
CallbackUrl = TwitterSettings.CallbackUri
};
var client = new RestClient
{
Authority = "https://api.twitter.com/oauth",
Credentials = credentials,
HasElevatedPermissions = true,
};
var request = new RestRequest
{
Path = "/request_token"
};
client.BeginRequest(request, new RestCallback(TwitterRequestTokenCompleted));
}
and
private void TwitterRequestTokenCompleted(RestRequest request, RestResponse response, object userstate)
{
_oAuthToken = GetQueryParameter(response.Content, "oauth_token");
_oAuthTokenSecret = GetQueryParameter(response.Content, "oauth_token_secret");
var authorizeUrl = TwitterSettings.AuthorizeUri + "?oauth_token=" + _oAuthToken;
if (String.IsNullOrEmpty(_oAuthToken) || String.IsNullOrEmpty(_oAuthTokenSecret))
{
Dispatcher.BeginInvoke(() => MessageBox.Show("error calling twitter"));
return;
}
Dispatcher.BeginInvoke(() => AuthBrowser.Navigate(new Uri(authorizeUrl)));
}
and You can do the same with access token.
Have you checked to see if the TweetSharp Library supports Windows Phone 8?