API route (/api) authentication not working in laravel

From a fresh, unmodified install of the auth0-laravel-php-web-app login sample, login/logout works fine on the homepage (/), but when viewing any api route (/api/user is used in the example) while logged in, I receive error:

Can't initialize a new session while there is one active session already
Auth0\SDK\Exception\CoreException 
…/vendor/auth0/auth0-php/src/Auth0.php line 337

It seems adding the middleware “auth” or “auth:api” to an api route (routes/api.php) is what messes up the authentication. On non-api routes (routes/web.php) it works just fine. Can anyone help - I really need to get auth working on the api routes. All cookies/browser data were cleared before the test - completely clean slate.

Steps to reproduce:

  1. clone repo
  2. composer install
  3. rename .example.env to .env & add the 3 relevant auth0 config values
  4. php artisan key:generate
  5. view /api/user (after having logged in first)
1 Like

@maxtor - Thanks for your patience here, I’m still investigating this.

Edit: I don’t think that this API route was meant to be included as part of this sample. We don’t have anything about it in the quickstarts and it’s using the auth:api middleware configured to use the token driver. This is mentioned as part of Passport API authentication, a package that’s not even installed in the sample. All of this tells me that this route should not have been included with the web app sample.

That said, the use case you described previously is a question that comes up now and then so I’d like to make sure that our package here is capable of what you’re looking to do. Give me a day or so and I’ll put together a concept.

Thanks Josh - it would be great to be able to utilize basic auth-protected api routes for logged-in users, I’m not sure why it doesn’t work out of the box (using “auth” middleware, not even “auth:api”) but if you could tweak it, that would be great so it can be used an endpoint for ajax requests etc. (only for the currently logged-in user).

1 Like

It will be also helpful if you can share your feedback with our product teams through our feedback site:

@maxtor - Just checking in here …

I confirmed that the API route that’s there was not added as a part of this sample so I removed that to avoid any further confusion there.

So, what we’re now dealing with is just basic Laravel API authentication, which does fall outside of Auth0’s scope here but is interesting never-the-less. I explored the idea I mentioned earlier of a session-based API call but I wouldn’t recommend it as it goes recommendations for good API building. You’re also potentially opening yourself up for a CSRF attack if you start to handle anything besides a GET request. I’ll leave the links here since you can Google and find them but, again, not recommended:

The right way to do this is with Passport using the article I linked to before. We just want to issue a token for a JS request to your app’s API, nothing fancy, so we’ll follow the Consuming your API with JS section.

At the moment, thought, I’m not able to get this working with our Laravel module as it is built now. Part of the problem relates to one of the issues you opened about the Auth0User class not using the Eloquent model. Another is how the Auth0UserProvider is composed. Even with changes to both of those, though, I’m still getting the redirect. I’m not sure if we’re checking auth and redirecting too early (before Passport can check the token) or if we’re overriding what Passport is doing somehow.

That’s where I’m at currently. Again, this is a use case we definitely want to support but I need to find the time to focus on how to do this correctly, first, and then how we can alter the library to support it (which may require a major release).

Thanks for your patience and let me know if you discover anything in the meantime!

1 Like

@maxtor - Thanks for your patience here. I wanted to make sure I had a clear answer, as well as a path forward for our documentation, before I posted back.

I explored the Laravel Passport method a bit and I think we should support that flow but it’s going require some major changes in our module, as I mentioned above.

I think a better path forward would be to secure the API with Auth0 instead and use access tokens generated during login to authenticate users. I’m working on getting our quickstart in better shape (PR) to explain that step by step but I’ll summarize here while that’s being reviewed:

  1. Create an API in Auth0 and add the scopes you want. If this API is only used for logged-in users to access or manage their own information and you don’t need separate scopes for separate actions, you can skip adding any scopes. This is the first section of the current API quickstart.
  2. Follow the second section of the quickstart as well but, after publishing the settings, you’ll need to make sure your laravel-auth0.php config file includes the fields needed for API auth, shown in this Gist.
  3. The “Protect API Endpoints” is the same except we’re going to swap out the middleware code with what’s in this Gist. The public and private routes will stay the same.
  4. Finally, in your login handler (Auth0IndexController from the web app quickstart), you need to add the api_identifier value like so:
    public function login()
    {
        $authorize_params = [
            'scope' => 'openid email email_verified',
            'audience' => config('laravel-auth0.api_identifier'),
        ];
        return \App::make('auth0')->login(null, null, $authorize_params);
    }

This is what gives you back a JWT token you can use against the API. You’ll want to persist the access token (persist_access_token => true in the laravel-auth0 config) and get that token to the front end somehow (your best bet is probably a cookie on the response in a custom middleware).

If you use cookies, keep in mind that they might be encrypted (that’s true by default, part of the web middleware group) so you’ll need to decrypt those before you decode them (all core Laravel stuff). You’ll also need to expire the cookie on logout. I walked through these steps, including the cookie storage, and had it working like a charm.

This gets you a public and a private API route, skipping scopes since I don’t think you need it based on what you’ve described so far. If you do, I can pass that code along as it’s all going into the re-written quickstart.

If you try this out, let me know if it works as expected and, if not, where you’re getting stuck. We appreciate your feedback so far and glad to be fixing the gaps you found in our documentation and samples.

Thanks again!

Thanks for this awesome reply. I think your approach here makes a lot of sense and should def. be part of the Quickstart guide(s) and sample projects. One of the reasons I turned to Auth0 was to avoid having to use Laravel Passport as it would seem to duplicate much/part of the functionality offered by Auth0 natively (and honestly, documentation around using it for SSO purposes is severely lackign/very hard to come by, another plus for Auth0).

In the meantime are you able to provide the code for the working sample project you mentioned (either with or without API scopes working)? Would be useful as I’m not very familiar with building out custom middlewares and/or best practises re: security considerations when it comes to this. That’s what I look to all-in-one solutions like Auth0 and it’s Libraries/SDKs for :wink:

Thank you once again for excellent support and the (much needed!) Laravel love.

Happy to help and thank you for the feedback on all this!

I think your approach here makes a lot of sense and should def. be part of the Quickstart guide(s) and sample projects.

I agree. The quickstart is just about complete (adding a bit more information and submitting for review) and I’ll think about how to incorporate some of the extras in a way that’s helpful.

One of the reasons I turned to Auth0 was to avoid having to use Laravel Passport as it would seem to duplicate much/part of the functionality offered by Auth0 natively

That’s a really good point, thanks for bringing that up. I was thinking our module should at least play nicely with Passport but they do the same job so maybe that’s not necessary.

In the meantime are you able to provide the code for the working sample project you mentioned (either with or without API scopes working)?

Absolutely! Are you clear on the specific changes from the last reply, the 4 steps? Again, those will all be in official documentation soon but want to get you unstuck now, if possible.

1 Like

Yes those 4 steps definitely made sense and worked great! Just missing the last pieces there to get API authentication working (hence requesting sample code or further steps) :slight_smile:

Absolutely, I just wanted to make sure that we were starting from the same place and that the information so far was clear.

Since this is all new guidance and code samples, I want to be careful about what I’m recommending here. Give me a business day or so to make sure this is working as expected and follows our best practices and I’ll post back when it’s ready.

Thank you!

1 Like

Thanks again for your patience here.

I consulted with a few folks internally and wanted to revisit something I mentioned earlier about cookies. What we’re exploring with the API token is best used in the case where you have an API that’s accessed by something that requires tokens (like a native app or another machine) as well. If you’re just getting data to, say, asynchronously load a profile or content based on preferences, you can probably get away with the cookie-based auth for those endpoints (covered in links above). You’ll need to make sure CORS is not active for your server (so other domains cannot use these cookies to get the same data) and use CSRF tokens if you ever start to accept POST requests.

With that said, I’ll pick up from where the previous steps above left off:

  1. Create a custom middleware to look for an access token (Gist) and save it to a cookie if there is one.

  2. Register this middleware in your HTTP Kernel:

// app/Http/Kernel.php
class Kernel extends HttpKernel {
    // ...
    protected $routeMiddleware = [
        // ...
        'setToken' =>  \App\Http\Middleware\SetAccessTokenCookie::class,
    ];
    // ...
}
  1. Now, we need to attach the setToken middleware to your callback route. This will run the code exchange early and set the user, which will be picked up by the controller. The final route should look like this:
Route::get( '/auth0/callback', '\Auth0\Login\Auth0Controller@callback' )
    ->name( 'auth0-callback' )
    ->middleware('setToken');
  1. Finally, we’ll exclude this cookie from encryption because it does not contain any sensitive information. In app/Http/Middleware/EncryptCookies.php, add the constant use for the cookie name:
protected $except = [
    \App\Http\Middleware\SetAccessTokenCookie::COOKIE_NAME
];

Now, when you login, you should have an access_token cookie accessible by JS with the token from Auth0. If you test that against the API built with the previous steps you should be able to access the endpoints there.

Hopefully this helps out with authentication for anything else that might be consuming this API.

1 Like

This seems to work pretty well, thank you for the excellent writeup and direction. Hope this makes it into the official docs and quickstarts.

One question I still have: while this middleware (checkJWT) successfully protects the API endpoints, how best can I access the current user? For e.g. I could use the Auth0Api->userinfo($access_token) call to retrieve the user’s “sub” (auth0 id), find that sub in my app’s db and then display the relevant user info. But that seems cumbersome as it requires an Auth0 API call for every API endpoint accessed - surely there are better ways? An example use-case would be a native app, where a user logs in via Lock.js or other native auth0 library, and then the app makes a request to mystore.com/api/user/orders to retrieve various orders placed by this user.

Lastly, if possible, it would be great to have a quickstart for both this method (API being accessed via token) and one for API for asynchronously loading. :slightly_smiling_face:

Happy to help and glad it works for you. Most, if not all, of this will be added to the documentation. The “calling your own API” is a little tricky because we start to get involved into really specific architecture requirements for your app, rather than authentication recommendations on our part. The API protected by an Auth0-issued token will be part of the quickstart, the API protected by a cookie is just part of your Laravel app so probably not.

how best can I access the current user?

The access token that’s provided is meant for your API to consume so you can decode that to get the sub value needed (as shown in the CheckJWT middleware here)

1 Like

Since accessing the user ‘sub’ value in the API is fairly essential stuff, we could just pass a custom header in the response from the CheckJWT middleware, such as:

$request->headers->set('Auth0-Sub', $decodedToken->sub);
return $next($request); 

This worked well in my testing so far.

Thanks a lot @maxtor for sharing it with the rest of community!

This topic was automatically closed 15 days after the last reply. New replies are no longer allowed.