auth0 / laravel-auth0

Laravel SDK for Auth0 Authentication and Management APIs.
MIT License
246 stars 136 forks source link

Access Token from Mobile Application does not work in Laravel #294

Closed travisobregon closed 2 years ago

travisobregon commented 2 years ago

SDK Version

7.0

PHP Version

PHP 8.1

Composer Version

2.x

What happened?

My goal is to use this package with a react native mobile application.

I have setup this package in Laravel and created a Custom API application in Auth0. Getting a token from the "/oauth/token" endpoint works, but it is not tied to a user.

I have then created a mobile app using the sample repo. When I log in via the app, I get an access token, but it returns a 403 when accessing a protected route in Laravel. The token does work on Auth0's "/userinfo" endpoint though.

How can we reproduce this issue?

  1. Create a Laravel API application
  2. Create a mobile app
  3. Sign in to the mobile app and copy the access token you get back.
  4. Use the access token and visit a protected route, such as:
    Route::get('/api/private', function () {
    return response()->json([
        'message' => 'Hello from a private endpoint! You need to be authenticated to see this.',
        'authorized' => Auth::check(),
        'user' => Auth::check() ? json_decode(json_encode((array) Auth::user(), JSON_THROW_ON_ERROR), true) : null,
    ], 200, [], JSON_PRETTY_PRINT);
    })->middleware(['auth0.authorize']);

Additional context

No response

evansims commented 2 years ago

Hi @travisobregon 👋 Thanks for reporting your issue. There isn't any reason off the top of my head that an access token generated from the React Native sample app wouldn't work with Laravel. My guess is there might be a misconfiguration between these two apps that is causing the token validation to fail.

Try hooking into the Auth0\Laravel\Event\Stateful\AuthenticationFailed event within your Laravel application and use the getException() method on that event to determine the exact reason your authorization is failing.

Do you have a sample application repo you could provide to demonstrate what you're seeing? This would help in diagnosing.

travisobregon commented 2 years ago

I tried listening for the AuthenticationFailed event, but it wasn't firing. I guess it is because I am using the "api" strategy, so it is "stateless"?

I noticed the react native library also gives an idToken. It still doesn't work with Laravel, but it validates on https://jwt.io/, unlike accessToken. Do you know what the difference is between the two?

Here is an example repo with a Laravel backend and react native app. If you need me to explain anything with them, let me know.

Thanks for your help.

evansims commented 2 years ago

Hey @travisobregon 👋 Thanks for being patient, I've been out on vacation and just getting back and catching up on things. I appreciate you creating that example repo, too.

I had some trouble getting your mobile project to build for some reason, but I created a fresh clone of auth0-react-native-sample and used that for these purposes. I was able to get that working with a few modifications, and I think I see your issue.

I suspect the issue you are facing is a lack of audience (Auth0 API Identifier) being configured. If this is not used, you'll receive what's called an “opaque access token” from the native SDKs, which won't work for these purposes (handing off to our backend.)

I modified the cloned repo's App.js to add an audience parameter at authorization time, like so:

const onLogin = () => {
        auth0.webAuth
            .authorize({
                audience: 'YOUR_API_IDENTIFIER_HERE', // <- The important change
                scope: 'openid profile email'
            })
            .then(credentials => {
                console.log(credentials);
                setAccessToken(credentials.accessToken);
            })
            .catch(error => console.log(error));
    };

Replace YOUR_API_IDENTIFIER_HERE with the API identifier assigned from your Auth0 Dashboard when you create the custom API. After this change, the native SDKs were returning standard access tokens and the Laravel SDK was authorizing using them just fine. Just be sure to set up the audience on both your mobile and Laravel backend, so the token's claims will validate successfully.

I tried listening for the AuthenticationFailed event, but it wasn't firing. I guess it is because I am using the "api" strategy, so it is "stateless"?

You're quite right, my apologies; I forgot you were using stateless in this case, so that wouldn't have worked. Whoops!

I noticed the react native library also gives an idToken. It still doesn't work with Laravel, but it validates on https://jwt.io/, unlike accessToken. Do you know what the difference is between the two?

ID Tokens are valid JWTs, but you wouldn't want to hand those off to a backend to act in place of an access token. Here's a good primer on the difference between the two token types: https://auth0.com/blog/id-token-access-token-what-is-the-difference/

travisobregon commented 2 years ago

Thanks. Adding audience adds progress. auth()->check() now works on a route using the 'auth0.authorize' middleware, but auth()->user() doesn't return anything useful about the user, such as name, or email. I get the following keys from it:

The scope I am using is "openid profile email". Trying to figure this out now.

evansims commented 2 years ago

Hey @travisobregon 👋 No worries!

Although user profile data is available from an ID Token, Access Tokens don't include that information. This is intentional and one of those differences between the two token types: authentication/identify (ID) vs authorization (access). You wouldn't want to pass identifying information to an endpoint at every network request, just the minimum necessary to authorize access to that endpoint.

Although you could use your access token to retrieve this information from the /userinfo endpoint, this is costly in terms of rate limiting and network bandwidth. Our general recommendation is to use Actions/Rules to add this information to the access token payload instead. These are set up in the Auth0 Dashboard and use bits of JS to manipulate the response data returned by the API, to customize to your needs.

You can find these on the Dashboard, under Auth Pipeline > Rules. I believe there is actually a template offered for adding emails to access tokens that you could build off of for your needs.

travisobregon commented 2 years ago

Okay, thanks. I guess at no point fromAccessToken will have attributes like "name" in $user?

I am just looking for the best way to handle users on the Laravel side now. I suppose keeping Auth0 as the single source of truth for a user's details is best? Then in my app's database I'd only really need an email column on the users table? The reason for a users table in my database would just be for associating a user with other models, like a post.

Does that seem sensible?

evansims commented 2 years ago

I guess at no point fromAccessToken will have attributes like "name" in $user?

By default at least, that's correct. However, you could expand the Rule approach I mentioned above to include any additional user details you'd like in your Access Token by expanding the template code as a starting point. There are also quite a few examples of this shared by users on the Auth0 Community, if you go searching there.

I suppose keeping Auth0 as the single source of truth for a user's details is best? Then in my app's database I'd only really need an email column on the users table? The reason for a users table in my database would just be for associating a user with other models, like a post.

As email addresses can change, I would recommend instead storing the sub value of the Access Token as the identifier to tie accounts in your app's database to your Auth0 user storage. The sub value is a string that will look something like auth0|123456. This is Auth0's unique identifier for the account that won't ever change, so you can easily associate users in your local database against incoming network requests from access tokens.