unauthorizedRedirect set to false, still redirecting - authentication

I'm writing some REST api for my cake 3.0 application, and I need to set $this->Auth->unauthorizedRedirect to false, as the manual says that this would prevent my application to redirect to login url for unauthorized requests.
http://api.cakephp.org/3.0/class-Cake.Auth.BasicAuthenticate.html
The problem is that I'm trying to set it in my Users controller, and it doesn't work:
class UsersController extends AppController {
public function initialize() {
parent::initialize();
$this->loadComponent('RequestHandler');
}
public function beforeFilter(Event $event) {
parent::beforeFilter($event);
$this->Auth->allow(['logout']);
// Change the authentication mode when using REST api
if(! $this->RequestHandler->accepts('html')) {
$this->Auth->unauthorizedRedirect = false;
$user = $this->Auth->identify();
if ($user) {
$this->Auth->setUser($user);
}
}
}
This scripts works fine as detecting if a user is actually registered, but fails when I try to use wrong authentication data, showing the login form instead of throwing an error. What am I doing wrong?

Authentication and authorization are two different things
You are mixing up authentication and authorization, that's two different things. Logging in a user is authentication, testing whether a logged in user is allowed to access a specific action is authorization.
So the unauthorized redirect configuration applies to logged in users when accessing actions.
Handling unauthenticated requests
What you are looking for, ie throw an exception on unauthenticated requests, is done by the basic authentication adapter by default, so I assume that you actually aren't using this adapter!?
So if you are using a different adapter, this behavior is best implemented in either your controller where you are trying to identify the user
$user = $this->Auth->identify();
if (!$user) {
throw new ForbiddenException('Stop! Hammer time!');
} else {
$this->Auth->setUser($user);
}
or, in case you want the exception to be thrown for every controller, in a custom authentication adapters unauthorized() method, which is being invoked on unauthenticated requests before executing possible redirects. Quote from the docs:
Cookbook > Authentication > Handling Unauthenticated Requests
When an unauthenticated user tries to access a protected page first the unauthenticated() method of the last authenticator in the chain is called. The authenticate object can handle sending response or redirection by returning a response object, to indicate no further action is necessary. Due to this, the order in which you specify the authentication provider in authenticate config matters.
If authenticator returns null, AuthComponent redirects user to login action. [...]
Here's a simple example that extends the form authentication handler:
src/Auth/MyCustomAuthenticate.php
namespace App\Auth;
use Cake\Auth\FormAuthenticate;
use Cake\Network\Exception\ForbiddenException;
use Cake\Network\Request;
use Cake\Network\Response;
class MyCustomAuthenticate extends FormAuthenticate
{
public function unauthenticated(Request $request, Response $response)
{
if(!$request->accepts('text/html')) {
throw new ForbiddenException('Ah ah ah! You didn\'t say the magic word!');
}
}
}
Controller
$this->loadComponent('Auth', [
'authenticate' => [
'MyCustom'
]
]);
See also
Cookbook > Authentication > Creating Custom Authentication Objects
Cookbook > Authentication > Using Custom Authentication Objects

Related

Using CakePHP Form and Basic Authentication together

I've created a simple test site using CakePHP 3.8 and Authentication 1.0 to try it out. I'd like to use both Form and Basic authentication since the intended app will offer REST calls.
The site works properly if the HttpBasic is not included, that is the Login window is displayed. However, with HttpBasic, the site goes directly to basic authentication.
The code is directly from the cookbook.
What am I missing?
public function getAuthenticationService(ServerRequestInterface $request, ResponseInterface $response)
{
$service = new AuthenticationService();
$service->setConfig([
'unauthenticatedRedirect' => '/users/login',
'queryParam' => 'redirect'
]);
$fields = [
'username' => 'user',
'password' => 'password',
];
// Load Identifiers
$service->loadIdentifier('Authentication.Password', compact('fields'));
// Load the authenticators
$service->loadAuthenticator('Authentication.Session');
$service->loadAuthenticator('Authentication.Form', [
'fields' => $fields,
'loginUrl' => '/users/login',
]);
$service->loadAuthenticator('Authentication.HttpBasic');
return $service;
}
As mentioned in the comments, using the form authenticator and the HTTP basic authenticator together won't work overly well, this is due to the fact that the authentication service won't stop executing all loaded authenticators, unless one of them returns a response that indicates successful authentication.
This means that you'd always be presented with the authentication challenge response, and never see your login form. Only the actual authentication part would work in that constellation, ie directly sending your login credentials as form data to the login endpoint.
If you don't actually need the basic auth challenge response that is preventing you from accessing the login form, then you could use a custom/extended authenticator that doesn't cause a challenge response to be returned, which should be as simple as overriding \Authentication\Authenticator\HttpBasicAuthenticator::unauthorizedChallenge():
src/Authenticator/ChallengelessHttpBasicAuthenticator.php
namespace App\Authenticator;
use Authentication\Authenticator\HttpBasicAuthenticator;
use Psr\Http\Message\ServerRequestInterface;
class ChallengelessHttpBasicAuthenticator extends HttpBasicAuthenticator
{
public function unauthorizedChallenge(ServerRequestInterface $request)
{
// noop
}
}
$service->loadAuthenticator(\App\Authenticator\ChallengelessHttpBasicAuthenticator::class);
Also not that you might need to add additional checks in case your application uses the authentication component's setIdentity() method, which would cause the identity to be persisted in the session, even when using stateless authenticators. If you don't want that, then you'd need to test whether the successful authenticator is stateless before setting the identity:
$provider = $this->Authentication->getAuthenticationService()->getAuthenticationProvider();
if (!($provider instanceof \Authentication\Authenticator\StatelessInterface))
{
$this->Authentication->setIdentity(/* ... */);
}

Symfony: Why some user checks should be performed after authentication?

I don't get this. UserCheckerInterface has two methods: checkPreAuth and checkPostAuth. Now let's look at their implementation in the class UserChecker:
class UserChecker implements UserCheckerInterface
{
/**
* {#inheritdoc}
*/
public function checkPreAuth(UserInterface $user)
{
if (!$user instanceof AdvancedUserInterface) {
return;
}
if (!$user->isAccountNonLocked()) {
$ex = new LockedException('User account is locked.');
$ex->setUser($user);
throw $ex;
}
if (!$user->isEnabled()) {
$ex = new DisabledException('User account is disabled.');
$ex->setUser($user);
throw $ex;
}
if (!$user->isAccountNonExpired()) {
$ex = new AccountExpiredException('User account has expired.');
$ex->setUser($user);
throw $ex;
}
}
/**
* {#inheritdoc}
*/
public function checkPostAuth(UserInterface $user)
{
if (!$user instanceof AdvancedUserInterface) {
return;
}
if (!$user->isCredentialsNonExpired()) {
$ex = new CredentialsExpiredException('User credentials have expired.');
$ex->setUser($user);
throw $ex;
}
}
}
Why should isCredentialsNonExpired() be done AFTER authentication? Shouldn't we just not allow the user with expired credentials to authenticate? And bonus question: Where should we really do this "post authentication" check? After setting the authentication token?
I believe the reason the methods are split is because when using session based authentication there are some things you don't want to check every time.
When using sessions, Symfony will serialize the token (and related user). When the next request comes in the PreAuthenticatedToken will contain the credentials you need for authorization.
Some examples of pre-authenticated tokens are: (stolen from docs)
authentication based on a "remember me" cookie.
authentication based on your session.
authentication using a HTTP basic or HTTP digest header
To improve performance if you have a token stored in the session you can remove some checks. The only example I have of the UserCheckerInterface is the one provided by Symfony. As you've seen, validation of the user's account is done inside preAuth and postAuth only checks if the credentials have expired.
In actual case looking at services that use these methods you can see that there isn't much distinction. The GuardAuthenticationProvider calls both sequentially.
Symfony's PreAuthenticatedAuthenticationProvider only calls postAuth so perhaps someone in Symfony decided that for session based authentication to shave a few milliseconds off the response time they could separate authentication checks that need to be done on first authentication from those that need to be done on every request.
In your case if you're creating a custom UserChecker I think you can decide from yourself if you need to use both. Find out if you have other bundles that have authentication providers calling either of these methods. Find all the places where they are called and you might find that you only need to implement one, or, if you have a lot of complex authentication logic, split it.

Why is my implementation of SSO using Ember-Simple-Auth with Auth0 getting stuck in a redirect loop?

I have an ember application that uses the Auth0 Ember Simple Auth addon to use the Ember-Simple-Auth functionality with Auth0's Lock.js. Recently I have been trying to implement single-sign-onfunctionality, such that if a user logs into a login portal application, their session will be preserved for other applications on the same domain, and they will not need to log in repeatedly. However my implementation of SSO is resulting in an infinite redirect loop between my login logic and Auth0's endpoint.
I have enabled SSO in the Auth0 application settings. My login is implemented in a few blocks.
My route.js contains a beforeModel() method which contains:
if (!get(session, 'isAuthenticated')){
// Forward to the login action
}
My login action:
login() {
var session = get(this, 'session');
session.authenticate('authenticator:myauthenticator', { authParams: { scope: 'openid' } });
}
This grabs the session object, and calls my custom authenticator. So far, this is basically just ember-simple-auth boilerplate, and complies with the examples supplied in the Auth0 Ember-Simple-Auth documentation.
Where I run into trouble is my custom authenticator. The base authenticator is here. You can see that it handles basic login logic easily, including showing the Auth0 lock when a user isn't authenticated. However it has no logic for handling the kind of SSO-session checking that I want to implement. So I implemented a custom authenticator as below, using examples provided by Auth0 for (basically) this exact scenario (you can see their examples [here], I'm using a slightly altered version)3:
authenticate(options) {
return new Ember.RSVP.Promise((res) => {
// the callback that will be executed upon authentication
var authCb = (err, profile, jwt, accessToken, state, refreshToken) => {
if (err) {
this.onAuthError(err);
} else {
var sessionData = { profile, jwt, accessToken, refreshToken };
this.afterAuth(sessionData).then(response => res(this._setupFutureEvents(response)));
}
};
var lock = this.get('lock');
// see if there's a SSO session available
lock.$auth0.getSSOData(function(err, data) {
if (!err && data.sso) {
// there is! redirect to Auth0 for SSO
options.authParams.callbackOnLocationHash = true;
lock.$auth0.signin(options.authParams, authCb);
} else {
// regular login
lock.show(options, authCb);
}
});
});
}
This behaves mostly as I would expect it to. When I log in with an existing session from another SSO-enabled app on the same domain, if (!err && data.sso) resolves to true, and lock.$auth0.signin(options.authParams, authCb) is called. However, this signin logic is not working as intended. Auth0.signin calls the Auth0.authorize method, which generates a target URL that looks something like:
https://mydomain.auth0.com/authorize?scope=openid&response_type=token&callbackOnLocationHash=true&sso=true&client_id=(MyClientIdHash)&redirect_uri=localhost%23access_token%3(MyAccessToken)%26id_token%3(MyIdToken1).(MyIdToken2).(MyIdToken3)token_type%3DBearer&auth0Client=(MyAuth0Client)
My application is then redirected to this URL for authorization. I get a 302 and am redirected back to the callback URL (my root page). Because there is a new page transition, if (!get(session, 'isAuthenticated')) is hit again. It returns false, and so the same logic repeats itself, looping indefinitely.
Does anyone have any insight on what I might be doing incorrectly here? The authorize endpoint seems to behave as if I were being authenticated, but then the authentication is never actually triggered. I've debugged through this code fairly extensively but seen no obvious red flags, and I've followed provided examples closely enough that I'm not sure what I would change. I'm not entirely sure where the failure to authenticate is happening such that get(session, 'isAuthenticated') is false.

Alternative to cookie based session/authentication

Is there an alternative to the session feature plugin in servicestack? In some scenarios I cannot use cookies to match the authorized session in my service implementation. Is there a possibility to resolve the session using a token in http header of the request? What is the preferred solution for that in case the browser is blocking cookies?
I'm using ServiceStack without the built-in auth and session providers.
I use a attribute as request filter to collect the user information (id and token), either from a cookie, request header or string parameter.
You can provide this information after the user takes login. You append a new cookie to the response and inject the id and token info on clientside when rendering the view, so you can use for http headers and query parameters for links.
public class AuthenticationAttribute : Attribute, IHasRequestFilter
{
public void RequestFilter(IHttpRequest request, IHttpResponse response, object dto)
{
var userAuth = new UserAuth { };
if(!string.IsNullOrWhiteSpace(request.GetCookieValue("auth"))
{
userAuth = (UserAuth)request.GetCookieValue("auth");
}
else if (!string.IsNullOrEmpty(request.Headers.Get("auth-key")) &&
!string.IsNullOrEmpty(request.Headers.Get("auth-id")))
{
userAuth.Id = request.Headers.Get("id");
userAuth.Token = request.Headers.Get("token");
}
authenticationService.Authenticate(userAuth.Id, userAuth.token);
}
public IHasRequestFilter Copy()
{
return new AuthenticationAttribute();
}
public int Priority { get { return -3; } } // negative are executed before global requests
}
If the user isn't authorized, i redirect him at this point.
My project supports SPA. If the user consumes the API with xmlhttprequests, the authentication stuff is done with headers. I inject that information on AngularJS when the page is loaded, and reuse it on all request (partial views, api consuming, etc). ServiceStack is powerful for this type of stuff, you can easily configure your AngularJS app and ServiceStack view engine to work side by side, validating every requests, globalizing your app, etc.
In case you don't have cookies and the requests aren't called by javascript, you can support the authentication without cookies if you always generate the links passing the id and token as query parameters, and pass them through hidden input on forms, for example.
#Guilherme Cardoso: In my current solution I am using a PreRequestFilters and the built-in session feature.
My workflow/workaround is the following:
When the user gets authorized I took the cookie and send it to the client by using an http header. Now the client can call services if the cookie is set in a http-header (Authorization) of the request.
To achieve this I redirect the faked authorization header to the cookie of the request using a PreRequestFilter. Now I am able to use the session feature. Feels like a hack but works for the moment ;-)
public class CookieRestoreFromAuthorizationHeaderPlugin : IPlugin
{
public void Register(IAppHost appHost)
{
appHost.PreRequestFilters.Add((req, res) =>
{
var cookieValue = req.GetCookieValue("ss-id");
if(!string.IsNullOrEmpty(cookieValue))
return;
var authorizationHeader = req.Headers.Get("Authorization");
if (!string.IsNullOrEmpty(authorizationHeader) && authorizationHeader.ToLower().StartsWith("basictoken "))
{
var cookie = Encoding.UTF8.GetString(Convert.FromBase64String(authorizationHeader.Split(' ').Last()));
req.Cookies.Add("ss-id",new Cookie("ss-id",cookie));
req.Items.Add("ss-id",cookie);
}
});
}
}

Forms Authentication with NancyFx

I am using NancyFx to make a simple website were users can login and out using an ajax control.
I have read the documentation on Forms Authentication with Nancy and I think I have completed all the required steps.
Install the Nancy.Authentication.Forms package
Implement an IUserMapper
Implement routes to handle login and logout
Configure and enable Forms Authentication
I am having an issue where after calling login, requests do not have a current user set.
I can see a cookie set after the login is executed. However the user mapper is not getting called. I have tried requesting routes with and with out the this.RequiresAuthentication(); and still no user.
Here is my Bootstrapper Implementation for step 4
public class Bootstrapper : DefaultNancyBootstrapper
{
protected override void ConfigureApplicationContainer(TinyIoCContainer container)
{
container.Register<ISessionFactory>((c, p) => SessionFactory.Factory);
}
protected override void ConfigureConventions(NancyConventions conventions)
{
base.ConfigureConventions(conventions);
conventions.StaticContentsConventions.Add(StaticContentConventionBuilder.AddDirectory("assets", #"content/assets"));
conventions.StaticContentsConventions.Add(StaticContentConventionBuilder.AddDirectory("application", #"content/application"));
}
protected override void ConfigureRequestContainer(TinyIoCContainer container, NancyContext context)
{
base.ConfigureRequestContainer(container, context);
container.Register<IUserMapper, UserMapper>();
}
protected override void RequestStartup(TinyIoCContainer container, IPipelines pipelines, NancyContext context)
{
base.RequestStartup(container, pipelines, context);
var formsAuthConfiguration =
new FormsAuthenticationConfiguration()
{
RedirectUrl = "~/",
UserMapper = container.Resolve<IUserMapper>()
};
FormsAuthentication.Enable(pipelines, formsAuthConfiguration);
}
}
Here is my login & logout logic for step 3.
Post["/login"] = x =>
{
//Verify here, hardcode for testing
string email = "test#example.com";
User user = ExecuteCommand(new FetchUser(email));
this.LoginWithoutRedirect(user.Session);
return new { email = user.Email, authorized = true, status = "okay" };
};
Post["/logout"] = x =>
{
return this.Logout("~/");
};
My IUsermapper simply looks up a user from a database with the given id.
I can see it gets constructed when the RequestStartup resolves the IUserMapper but then there are never any calls to the get GetUserFromIdentifier function.
Any help would be great.
The GetUserFromIdentifier method is not being called because you are using the LoginWithoutRedirect extension. It is not the login that calls GetUserFromIdentifier but rather any subsequent redirect.
A more usual way of doing things would be:
string email = "test#example.com";
User user = ExecuteCommand(new FetchUser(email));
this.LoginAndRedirect(user.Session);
It is not expected that the login route would be accessed directly. Instead the user would normally request a protected resource, be authenticated and then redirected to the requested resource.
A couple of other points:
When I tried your code I got an error returning an anonymous type. Instead I needed to return the type as json, like this:
this.LoginWithoutRedirect(user.Session);
return Response.AsJson(new
{
email = user.Email,
authorized = true,
status = "okay"
});
This works fine, it logs in and returns your anonymous type as a json object and since there is no redirect then, as expected, it does not call GetUserFromIdentifier.
Finally, your /logout route should be protected using this.RequiresAuthentication(). It makes sense because only authenticated users need to logout. It will also protect you when GetUserFromIdentifier returns null - perhaps because a cached user has been timed out. RequiresAuthentication detects this null user and redirects back to Get["/login"].