Auth0 and Asp.Net Core 2.0 Razor Pages LoginPath Issue - razor

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.

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);
}
};
});

Exception handling using Hellang middleware in .Net Core MVC

I've used Hellang Middleware for exception handling as the global exception handling mechanism in my MVC application.
I've added the following code in the ConfigureServices method in Startup.cs:
services.AddProblemDetails(opts =>
{
// Control when an exception is included
opts.IncludeExceptionDetails = (ctx, ex) =>
{
// Fetch services from HttpContext.RequestServices
var env = ctx.RequestServices.GetRequiredService<IHostEnvironment>();
return env.IsDevelopment() || env.IsStaging();
};
opts.ShouldLogUnhandledException = (ctx, e, d) =>
{
return (d.Status.HasValue && d.Status.Value >= 500);
};
});
Also I've added UseProblemDetails() in Configure method.
However I came to know that if am using UseProblemDetails(), then UseExceptionHandler() won't work!
Hence I'am not able to figure out a method for navigating user to a common error view page.
Is there any way to redirect users to an error page while sticking on to Hellang Middleware for exception handling and logging ?
See the answer here:
https://stackoverflow.com/a/40153711/90287
You have to distinguish between the type of request, if it's an API request or a UI request to determine if a problem+details JSON should be returned or if a web page should be returned, respectively.
This is what I do near the top of the Configure method of Startup.cs:
app.UseWhen(context => context.IsApiRequest(), branch =>
{
branch.UseProblemDetails();
});
app.UseWhen(context => !context.IsApiRequest(), branch =>
{
branch.UseExceptionHandler("/Home/Error");
});
You can define your own custom HttpContext extension method:
public static class HttpContextExtensions
{
public static bool IsApiRequest(this HttpContext context)
{
return context.Request.Path.StartsWithSegments("/api", StringComparison.OrdinalIgnoreCase)
|| (context.Request.Headers["X-Requested-With"] == "XMLHttpRequest"); // AJAX request
}
}
I had a similar problem. I solved it like the following. In this example logging a custom business fault exception:
services.AddProblemDetails(setup =>
{
setup.Map<FaultException<BusinessFault>>((context, exception) =>
{
// resolve logger
var logger = context.RequestServices.GetRequiredService<ILogger<ProblemDetails>>();
// log exception to Seq
logger.LogError(exception, "{#Exception} occurred.", exception);
// return the problem details map
return new ProblemDetails
{
Title = exception.Message,
Detail = exception.Detail.FaultMessage,
Status = exception.Detail.FaultType.ToHttpStatus(),
Type = exception.Detail.FaultType.ToString(),
Instance = exception.Detail.FaultReference
};
});
});
This is not exactly the answer to your question, but I had a similar issue in a Web API application regarding using ExceptionHandler middleware and Hellang ProblemDetails Middleware and I also came to realize I could't use them both because both change the response in their own way and affect one another.
Based on the documentation here you can use one of the configuration options of the ProblemDetails package to excute code before changing response and there you can log all the information you need.
services.AddProblemDetails(options =>
{
options.IncludeExceptionDetails = (context, ex) =>
{
var environment = context.RequestServices.GetRequiredService<IWebHostEnvironment>();
return environment.IsDevelopment();
};
options.Map<IdentityException>(exception => new ProblemDetails()
{
Title = exception.Title,
Detail = exception.Detail,
Status = StatusCodes.Status500InternalServerError,
Type = exception.Type,
Instance = exception.ToString()
});
options.OnBeforeWriteDetails = (ctx, pr) =>
{
//here you can do the logging
logger.LogError("Exception Occurred!!!!");
logger.LogError(pr.Detail);
logger.LogError(pr.Instance);
};
});
Here, I use a custom exception with extra fields that are needed for problem details object in response, and I use the Instance field to hold the exception and log it.

JSON web Tokens and cookie authentication in the same time

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?

Login to Chrome extension with a Google user other than the one in use by Chrome

I have a Chrome extension that requests a user to login using the chrome.identity.getAuthToken route. This works fine, but when you login you can only use the users that you have accounts in Chrome for.
The client would like to be able to login with a different Google account; so rather than using the.client#gmail.com, which is the account Chrome is signed in to, they want to be able to login using the.client#company.com, which is also a valid Google account.
It is possible for me to be logged in to Chrome with one account, and Gmail with a second account, and I do not get the option to choose in the extension.
Is this possible?
Instead of authenticating the user using the chrome.identity.getAuthToken , just implement the OAuth part yourself.
You can use libraries to help you, but the last time I tried the most helpful library (the Google API Client) will not work on a Chrome extension.
Check out the Google OpenID Connect documentation for more info. In the end all you have to do is redirect the user to the OAuth URL, use your extension to get Google's answer (the authorization code) and then convert the authorization code to an access token (it's a simple POST call).
Since for a Chrome extension you cannot redirect to a web server, you can use the installed app redirect URI : urn:ietf:wg:oauth:2.0:oob. With this Google will display a page containing the authorization code.
Just use your extension to inject some javascript code in this page to get the authorization code, close the HTML page, perform the POST call to obtain the user's email.
Based on David's answer, I found out that chrome.identity (as well as generic browser.identity) API now provides a chrome.identity.launchWebAuthFlow method which can be used to launch an OAuth workflow. Following is a sample class showing how to use it:
class OAuth {
constructor(clientId) {
this.tokens = [];
this.redirectUrl = chrome.identity.getRedirectURL();
this.clientId = clientId;
this.scopes = [
"https://www.googleapis.com/auth/gmail.modify",
"https://www.googleapis.com/auth/gmail.compose",
"https://www.googleapis.com/auth/gmail.send"
];
this.validationBaseUrl = "https://www.googleapis.com/oauth2/v3/tokeninfo";
}
generateAuthUrl(email) {
const params = {
client_id: this.clientId,
response_type: 'token',
redirect_uri: encodeURIComponent(this.redirectUrl),
scope: encodeURIComponent(this.scopes.join(' ')),
login_hint: email
};
let url = 'https://accounts.google.com/o/oauth2/auth?';
for (const p in params) {
url += `${p}=${params[p]}&`;
}
return url;
}
extractAccessToken(redirectUri) {
let m = redirectUri.match(/[#?](.*)/);
if (!m || m.length < 1)
return null;
let params = new URLSearchParams(m[1].split("#")[0]);
return params.get("access_token");
}
/**
Validate the token contained in redirectURL.
This follows essentially the process here:
https://developers.google.com/identity/protocols/OAuth2UserAgent#tokeninfo-validation
- make a GET request to the validation URL, including the access token
- if the response is 200, and contains an "aud" property, and that property
matches the clientID, then the response is valid
- otherwise it is not valid
Note that the Google page talks about an "audience" property, but in fact
it seems to be "aud".
*/
validate(redirectURL) {
const accessToken = this.extractAccessToken(redirectURL);
if (!accessToken) {
throw "Authorization failure";
}
const validationURL = `${this.validationBaseUrl}?access_token=${accessToken}`;
const validationRequest = new Request(validationURL, {
method: "GET"
});
function checkResponse(response) {
return new Promise((resolve, reject) => {
if (response.status != 200) {
reject("Token validation error");
}
response.json().then((json) => {
if (json.aud && (json.aud === this.clientId)) {
resolve(accessToken);
} else {
reject("Token validation error");
}
});
});
}
return fetch(validationRequest).then(checkResponse.bind(this));
}
/**
Authenticate and authorize using browser.identity.launchWebAuthFlow().
If successful, this resolves with a redirectURL string that contains
an access token.
*/
authorize(email) {
const that = this;
return new Promise((resolve, reject) => {
chrome.identity.launchWebAuthFlow({
interactive: true,
url: that.generateAuthUrl(email)
}, function(responseUrl) {
resolve(responseUrl);
});
});
}
getAccessToken(email) {
if (!this.tokens[email]) {
const token = await this.authorize(email).then(this.validate.bind(this));
this.tokens[email] = token;
}
return this.tokens[email];
}
}
DISCLAIMER: above class is based on open-source sample code from Mozilla Developer Network.
Usage:
const clientId = "YOUR-CLIENT-ID"; // follow link below to see how to get client id
const oauth = new OAuth();
const token = await oauth.getAccessToken("sample#gmail.com");
Of course, you need to handle the expiration of tokens yourself i.e. when you get 401 from Google's API, remove token and try to authorize again.
A complete sample extension using Google's OAuth can be found here.

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?