The code for MyCustomUserRepository here: Laravel API quickstart suggests that $profile->email and $profile->name will contain the user’s email and name (assuming these have been requested in the scope). However, Calling your APIs with Auth0 tokens suggests that these would be in the id_token and not in the access_token as, indeed seems to be the case when I try. Am I right in thinking that the code in MyCustomUserRepository is now out of date and that I need to make a call to the /userinfo api method to get these details? If so, do I just use cURL or are there any convenience methods in the Laravel-SDK that I can use?
Just to add to this, I was hoping to be able to get the access_token which I could use to call /userinfo from Auth0::getUser() but this just returns null… If I look in Auth0Service.php and log out $auth0 from getUser(), I get the below. The confusing thing is that, although the access-token is there as the value of the header, nothing else (scope, etc) is populated…I can’t tell whether this is because MyCustomUserRepository is trying to access this information before it has been populated…or whether something else has gone wrong. If the former, how am I supposed to access /userinfo so that I can follow the Laravel ‘quickstart’(!) and populate by Laravel DB with user data on login?
[2018-07-05 19:57:35] local.INFO: auth0: Auth0\SDK\Auth0 Object
(
[persistantMap] => Array
(
[2] => user
)
[domain:protected] => domain.eu.auth0.com
[clientId:protected] => clientId
[clientSecret:protected] => clientSecret
[responseMode:protected] => query
[responseType:protected] => code
[audience:protected] =>
[scope:protected] =>
[refreshToken:protected] =>
[redirectUri:protected] => http://localhost:4200/callback
[debugMode:protected] =>
[debugger:protected] =>
[accessToken:protected] =>
[idToken:protected] =>
[store:protected] => Auth0\Login\LaravelSessionStore Object
(
)
[user:protected] =>
[authentication:protected] => Auth0\SDK\API\Authentication Object
(
[client_id:Auth0\SDK\API\Authentication:private] => clientId
[client_secret:Auth0\SDK\API\Authentication:private] => clientSecret
[domain:Auth0\SDK\API\Authentication:private] => domain.eu.auth0.com
[apiClient:Auth0\SDK\API\Authentication:private] => Auth0\SDK\API\Helpers\ApiClient Object
(
[domain:protected] => https://domain.eu.auth0.com
[basePath:protected] => /
[headers:protected] => Array
(
[0] => Auth0\SDK\API\Header\Header Object
(
[header:protected] => Auth0-Client
[value:protected] => access_token
)
)
[guzzleOptions:protected] => Array
(
)
)
[guzzleOptions:Auth0\SDK\API\Authentication:private] =>
[audience:Auth0\SDK\API\Authentication:private] =>
[scope:Auth0\SDK\API\Authentication:private] =>
)
[guzzleOptions:protected] =>
[stateHandler:protected] => Auth0\SDK\API\Helpers\State\SessionStateHandler Object
(
[store:Auth0\SDK\API\Helpers\State\SessionStateHandler:private] => Auth0\SDK\Store\SessionStore Object
(
)
)
)
Hi @theotherdy,
I’m not sure I’m totally following your question but I’ll try to get you pointed in the right direction. If you have any questions, feel free to reply back.
It sounds like you might be using the wrong Quickstart here. The one you’re pointing to secures an API built in Laravel with an ID token. There is also one for regular web app authentication here. If you want an access token for a user to call /userinfo
, that’s likely the one you want to use.
As for the missing access token … do you have persist_access_token
set to true
in the config file? For the regular web app authentication, that happens here:
Just to add … the Laravel module uses our PHP SDK which has a number of built-in methods that do this work for you, including state validation. The web app quickstart linked above will walk you through all of that.
Thanks so much @josh.cunningham. Sorry if I wasn’t clear. I am putting together a new Angular 5 client which talks to a Laravel API - the Laravel Quickstart code from Github (linked to from the Quickstart guide). The Laravel API needs to persist users - there are various other tables related to users.
The MyCustomRepository provider in the Quickstart looks as if it should do exactly what I need (and in fact, in a previous applications did work using the code in the examples). However $profile referred to in the QuickStart does NOT contain any OIDC information - $profile now just contains the decrypted fields from the access_token. Given that Auth0 knows the user’s email, name, etc, I want to be able to save these in my Laravel API’s DB when the user first logs in. From reading around, it looks as if I will only be able to get this profile data from the id_token (which I have in my Angular client but not in the Laravel API).
So, as it stands, it looks as if the code in the Quickstart for Laravel APIs is now out of date (since Auth0 adopted the OIDC style of tokens?) and the code presented for MyCustomRepository no longer works?
So, my question is: how can I access the profile information which is contained in id_token from my Laravel api. Currently, I’m having to make a cURL call from MyCustomRepository to the Management API to get another access token which then allows me to get the user data - this seems very inefficient - is there a better way of doing this?
Many thanks
Damion
Jut re-read your first comment @josh.cunningham and note that you say that the quickstart I’m pointing to ‘secures an API built in Laravel with an ID token’. I think I must be missing something here as I’m actually passing an access token to it from my angular 5 app, following the Angular quickstart and using the sample angular code from GitHub. Does this mean that I actually need to be calling my api by passing in the ID token somehow? As well as the access token?
@theotherdy - It sounds to me like you’re trying to combine the two tasks of user authentication (the quickstart I linked to) and API access (the one you’ve been following). I think we (Auth0) helped to make that happen by including that custom user repository code in the API sample, I think that’s misplaced (checking now with the team that handles that).
The “ID Token” you’re using to access the API is not an ID token at all, though it follows the same JWT format. If you drop that into our JWT tool, you’ll see that there is no user information at all since this is just meant to allow two machines to communicate. The machine wanting access should do a client credentials grant with Auth0 for the API it wants access to, then use that resulting token to access the API.
If you’re looking to authenticate a user, it’s the web auth quickstart you’ll want to follow. The ID token you receive as a user (this time it is an ID token) will have the profile information you need to authenticate (just set the scopes
you need). You can store that token in the browser and use it against the API.
The one place where this will differ a bit is API scopes. The ID token does not have any API scopes associated with it so you’ll need to either manage that in your app (when you persist the user, also include a role of some kind used to determine authorization) or just use their successful authentication as “can access all private endpoints”.
Thanks @josh.cunningham - very helpful. It sounds as if I’m now doing what you would expect me to do - calling Auth0’s API once I have a user (authenticated on the client) hitting my Laravel API. However, I wouldn’t be too quick to get rid of the custom user repository code - the upsertUser method (which previously has the confusing $profile->email, etc reference) seems to be the ideal place to make my calls to the Auth0 API to get the data I need to create my user in my local DB. I’ve included the code I’m using (I’m still a Laravel and cURL novice though) in case it’s useful for anyone else landing here. Given that there seem to be rate limits on calling APIs, do note my TODO to store the Auth0 API token locally and check it before asking for a new one. Any suggestions for how to improve on this very welcome
protected function upsertUser($profile) {
// Note: Requires configured database access
$user = User::where("auth0id", $profile->user_id)->first();
if ($user === null) {
// If not, create one
//TODO Save token in DB so doesn't have to be generated on every request
//first check whether we have one which hasn't expired
//otherwise, get a new one
//Make Post Fields Array - values from config/myapp.php overridden by .env in root
$tokenData = [
'grant_type' => config('myapp.auth0Management.grant_type'),
'client_id' => config('myapp.auth0Management.client_id'),
'client_secret' => config('myapp.auth0Management.client_secret'),
'audience' => config('myapp.auth0Management.audience')
];
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => "https://mydomain.eu.auth0.com/oauth/token",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode($tokenData),
CURLOPT_HTTPHEADER => array(
"content-type: application/json"
),
));
$token_response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
Log::info('Getting Management api token - cURL Error #: '. $err);
} else {
Log::info('Got token!'. print_r($token_response, true));
//now use our token to get userinfo
$token_response = json_decode($token_response);
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => "https://mydomain.eu.auth0.com/api/v2/users/".$profile->user_id,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => array(
"content-type: application/json",
"authorization: Bearer ".$token_response->access_token
),
));
$get_user_response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
Log::info('Getting user - cURL Error #: '. $err);
} else {
//all OK
$user_response = json_decode($get_user_response);
$user = new User();
if(isset($user_response->email)){
$user->email = $user_response->email;
}
if(isset($user_response->picture)){
$user->picture = $user_response->picture;
}
$user->auth0id = $profile->user_id;
$user->save();
}
}
}
return $user;
}
Awesome, @theotherdy, great to hear!
About the user repo … The one on the API quickstart doesn’t make a lot of sense for the reason that sent you here. The API access does not rely on a user so I think that’s more confusing than anything. The one on our web auth quickstart is more up-to-date and make sense where it is. Checking with the internal team now to see why it’s on the API doc.
A few suggestions on your code above, FWIW:
- When you store that ID token, do so securely. Remember that if that leaks, it can be used to access the API. A quick search brought me here. Also, make sure your scopes for that API token are restricted for only what you need (looks like
read:users
is all you need here). - On that note … remember that you have the ID token for the user data you need instead of hitting the Management API. You’re only using email, picture, and user_id, all of which can be found in an OIDC-compliant ID token. If you end up needing additional data, you can add custom scopes
-
$tokenData['grant_type']
should not change much or ever, probably best to hard-code that. - I’d recommend using an HTTP client rather than a direct cURL call. We use Guzzle in the PHP-SDK and it works great to abstract some of that extra code away.
Thanks again @josh.cunningham. Thanks for all these top tips - very useful indeed. Could I ask for one more bit of advice? How do I access data - specifically the [sub] - about the logged in user in other parts of my api application from the Request data which my Laravel controller functions receive? With the previous Auth0-protected Laravel api I wrote (a couple of years ago), the code you provided nicely populated a >user() method on the request which I could use to get information. Despite the fact that the Laravel api quickstart (variously!) says “You will now be able to access user info with Auth0::getUser()” and “At any point you can call Auth::check() to determine if there is a user logged in and Auth::user() to retrieve the wrapper with the user information”, neither Auth0::getUser() nor Auth::user() contain anything! Any hints about where to look to join this up again using the recent QuickStart code?
Of course, always happy to help!
The PHP SDK repo might have some information you can use:
In short, that Auth0::getUser() method should have the data you’re looking for, assuming you have sessions working properly. Depending on how you want to structure the app, you can pull from that or you can implement your own getUser()
- type function to pull from what Laravel has instead (if you’re storing/syncing the profile from Auth0, it might be best to get all your user data directly from your apps store … at the very least, you won’t have to look in 2 places).
Again, I wouldn’t use the API quickstart to do anything with users since that’s just using a token to access data. Assuming the token is valid, your user retrieval should happen as it would with a regular web app.
Let me know if you need any clarification here or if I misunderstood your question.
Thanks @josh.cunningham, that sort of makes sense… I am indeed intending to pull all user info from Laravel’s DB. However, I need to know who the authenticated user is when they hit methods in the api - I don’t want to rely on passing the user ID from the client separately when it is already there in the token. Obviously MyCustomUserRepository ‘knows’ who the user is when they authenticate - my question was whether there are any convenience methods which will let me find out the [sub] claim from the access token in my application’s controllers…I’m not sure that the Laravel api implementation I am using (the Auth0 API quickstart) really has sessions as don’t think they make sense in an api?
my question was whether there are any convenience methods which will let me find out the [sub] claim from the access token in my application’s controllers
I think you mean “ID token” and, if so, that can be parsed with the following method in the PHP SDK:
https://github.com/auth0/auth0-PHP/blob/master/src/JWTVerifier.php#L103