Identity Server 4: Redirect or Show Error Message in OnTokenValidated - identityserver4

In my web application, after user authenticates w/ interactive login page, I would like to do some additional validation in OnTokenValidated after the user is authenticated, and if it fails then i want to show a nice page with an appropriate message.
How can I do this? I tried:
private Task HandleOpenIdConnectOnTokenValidated(TokenValidatedContext context)
{
if (some condition fails...)
{
context.HttpContext.Response.Redirect("/home/notregistered");
return Task.FromResult(-1);
}
}
return Task.CompletedTask;
}
Other option is to throw an exception, but it shows an unpleasant error. If this is best/only option, how can I specify a custom error page and show something custom based on the exception thrown?
thanks

I would implement the additional authorization requirements using the built in authorization mechanisms.
Perhaps using IAuthorizationRequirement, as described here. It allows you to create very flexible authorization rules.

Related

How to execute custom code after successful authorization?

Client app is redirecting to IdentityServer4 instance - "/connect/authorize/callback"?client_id=...".
If the user is not logged in(has no cookie) he is redirected to the Login page and on postback I can execute random logic(subscribe the user to this client - add claim to the user's claims collection with the client_id).
If the user has valid cookie there is no login screen and IdentityServer4 redirects him back to the Client app with the requested tokens in the query string (the consent is turned off).
How can I execute custom code when this event occurs?
A "login success" event is available from the Built-In Events. Unfortunately, Identity Server events are logging-oriented, so you must implement IEventSink to capture events, and you probably can't do things like show UI without interrupting the rest of the OIDC flow. This also means you can't log events unless you manually handle it in your IEventSink implementation (see the DefaultEventSink, it just serializes the event to whatever logger is registered for injection). The event is raised by the TokenRequestValidator.
First, enable "success" events in the configuration...
services.AddIdentityServer(options =>
{
options.Events.RaiseSuccessEvents = true;
});
...then look for the UserLoginSuccessEvent.
public class LoginEventSink : IEventSink
{
public Task PersistAsync(Event evt)
{
if(evt.Id.Equals(EventIds.UserLoginSuccess))
{
// do stuff
}
}
}
One of the overloads tells you whether the login was interactive by setting Event.Endpoint to UI but I don't see a way to get that from IEventSink, not really sure how it's used. You might be able to check the OIDC "Prompt" mode for None if you need that.
Depending on your setup and requirements, you could also capture an event on the client side when the challenge returns, but it sounds like you want to keep this in Identity Server.

Should I be testing for an AdalException inside an ActionFilter?

In some Azure Samples on GitHub like this one we have an example of using ADAL to access a protected Web API resource. The effort is protected by a try/catch looking for an AdalException
I'll summarize the code as thus:
try
{
//pseudo code... configure a client to use an access token..
var token = ADAL.AcquireTokenAsync...
var httpClient = new HttpClient(baseUri, token);
// use the token for querying some protected API
var result = //await HttpClient to return data...
return View(result);
}
catch (AdalException)
{
// do something important with the exception,
// e.g. return an error View w/login link
}
So, as I start to flesh out my MVC controllers to use ADAL-access_token'd requests, do I really want all this try/catch business in every controller?
Does it make sense to create an ActionFilter? This snippet was inspired by code I saw at this Azure Sample
public class AdalErrorAttribute : FilterAttribute, IExceptionFilter
{
void IExceptionFilter.OnException(ExceptionContext filterContext)
{
if(filterContext.Exception is AdalException)
{
if (filterContext.RequestContext.HttpContext.Request.QueryString["reauth"] == "True")
{
//
// Send an OpenID Connect sign-in request to get a new set of tokens.
// If the user still has a valid session with Azure AD, they will not be prompted for their credentials.
// The OpenID Connect middleware will return to this controller after the sign-in response has been handled.
//
filterContext.RequestContext.HttpContext.GetOwinContext().Authentication.Challenge(
new AuthenticationProperties(),
OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
}
}
}
My Context:
I'm taking a rather homogenous set of scaffolded MVC controllers that were EntityFramework-centric at the time they were generated.. but now need to be re-tooled to access my Web API (via my new AutoRest client)
#vibronet made a good point - don't do that for your WebAPI endpoints. They're called with bearer auth, and shouldn't be automatically redirected to a sign-in process. Give back a 401 indicating the credentials are invalid and let it go.
But for an MVC app the user is using interactively, this is a reasonable idea to follow, subject to a couple of constraints.
Make sure your filter is very tight about what it matches. Ie. make sure it's only handling exceptions you can reasonably-certainly draw to an authentication issue (vs. being an access-denied issue). Perhaps a custom exception that gets thrown by your wrapping logic, or some extra conditions beyond it being an ADALException to make sure it's an issue solveable by logging in again.
Unless you really want every request have this handling, look at attaching it at the controller or action layer instead of globally.
Look out for potential "loop" issues, where you get an error, tell the user to log in again, get the error, make them log in again, etc. Maybe set some data in the session when triggering the login, or in the login process, or something like that. Better to give an error message than get the user's browser stuck in an infinite-redirect loop. (of course, this applies to the manually-handle-the-exception case too)
Hope that helps.
There are two issues with the approach.
the sample code you mention and the filter implementation are for web APPs, not web API. That's an important difference. Web APPs can rely on the browser to interact with the user for authentication; web APIs don't know what UX the client has (or even if there's any UX at all) hence the error behavior is radically different
a controller might call multiple APIs, requiring multiple token requests for different resources- which can fail or succeed independently, and connect to different providers. Different controllers might call different resources. The reaction to the error might vary. A filter such as the one you prototyped would only help with a very narrow set of cases.
Hence I would say that unless your controllers are all expected to perform very homogeneous logic, a filter such as the above would not give you the flexibility you need.

Different login errors for custom CakePHP authentication

I'm doing a site that requires double authentication, one from LDAP and one from local database. I did a custom authentication extending BaseAuthenticate and implementing custom authenticate() method, but as documentation says:
Authentication objects should return false if they cannot identify the user. And an array of user information if they can.
I need to show an "invalid credentials" error when the user doesn't pass the LDAP authentication, and "access error, please contact the administrator" error when he isn't on the database, but I can't find a way to provide two different errors, as failed login just have to return false.
So, is there a way to show different messages?

How do I bypass a fatal error handling in CakePHP custom Authenticator Object?

I'm doing a soap based authentication in my custom Authenticator object. Sometime the soap endpoint may not be available. This in sequence will throw a
SOAP-ERROR: Parsing WSDL: Couldn't load from
and then will be handled by
ErrorHandler::handleFatalError(1, 'SOAP-ERROR: Par...', '/home/bombadil/...', 100)
in lib/Cake/Error/ErrorHandler.php
and then the page will be redirected to lib Cake/View/Errors/fatal_error.ctp
Now, how do I redirect to my login page again and showing a toned down error message instead of throwing the unfriendly page of fatal_error.ctp? I can't afford to create another fatal_error page because I want to redirect again to my login page.
Thanks
NVM, I raise an exception upon unsuccessful connection in the Soap datasource and catch it at my model which doing the Soap based authentication.

CakePHP 2 and Croogo with Basic Authentication for REST

When trying to login via REST, I simply added:
if ($this->RequestHandler->isXML()) {
$this->Auth->authenticate = array('Basic');
}
To my plugins AppController. When i request via a rest client i get:
DbAcl::check() - Failed ARO/ACO node lookup in permissions check.
With my user object dumped. Strange, it seems like it's logging me in but not reading permissions correctly?
When i added:
$this->Auth->authorize = array('Controller');
and implemented the isAuthorized method:
public function isAuthorized($user) {
if (isset($user['role_id']) && $user['role_id'] == 1) {
return true; //Admin can access every action
}
return false; // The rest don't
}
It worked.
There is one fundamental thing here that you seem to be missing: Authentication and Authorization are two different things.
Authentication is the process of identifying users by provided credentials and ensuring that users are who they say they are. This is generally done using an username and password.
Authorization is the process of ensuring that the current user is allowed to access a
specific resource.
The error you were getting is an Authorization error and from it I call tell that you're trying to use ACL. Your fix to the issue is to configure Controller based Authorization andthrough it allow the admin to access everything. Controller Based Authorization is not related to ACL authorization. Check the links provided.
The AuthComponent implements 3 different types of Authentication:
FormAuthenticate - allows you to authenticate users based on form POST
data. Usually this is a login form that users enter information into.
BasicAuthenticate - allows you to authenticate users using Basic HTTP
authentication.
DigestAuthenticate - allows you to authenticate users using Digest
HTTP authentication.
If you're developing a REST service I'd recommend using Digest Authentication as the "best to have" but it'd still depend on what you're logging in.

Resources