Closed PCoetzeeDev closed 7 years ago
what @SturmDrangAltaar said, also going to try it in a production environment. :) Searching for a OAuth2 package for simple token authentication, this will provide that right?
please keep us posted.
thanks!
Is there any documentation how to use it in Lumen? Thank you.
@nmfzone do you man https://github.com/dusterio/lumen-passport/blob/master/README.md ? Or what do you want to know, specifically?
@DCdeBrabander No, I mean the real implementation when use in Lumen. Like how to logging in when using Passport, etc. I think the implementation is slightly different with Laravel before. But, forget it, I've successfully implemented Passport using your package, thank you very much.
@nmfzone Aah, well good you had no more problems :) for the record. It is @dusterio 's package, I'm just trying to help and get information in any way, since I'm implementing this package myself probably tomorrow. I just want to be sure if I need to know more before I try to implement this in a production environment.
@nmfzone can you explain, in short, how you did it and especially where to keep an eye on. I only need to implement a simple oAuth process where clients with (pre-given) id+secret+token have to do requests and refresh there tokens
@DCdeBrabander Whoops, I'm sorry. I think you're the owner of this package, because your name starts with 'D' :smile: I think you could see my code here. My implementation located in LoginController. Fyi, I use Password grant.
@nmfzone I will probably use 'Password Grant' aswell, since that approach matches our (client's) wishes the most. I have some problems setting it up though. Lib complaining about User model (and its dependencies), and bit of trouble finding the correct way to send all params to the Passport providers. I was now using Guzzle in Controller aswell. Can you eolaborate on how you came up with your solution? :)
@DCdeBrabander Whoops, I think it would be a long explanation. But, it's okay. First, what version of Lumen do you use?
"Lib complaining about User model (and its dependencies)" ? Could you provide some screenshot or maybe a bit of explanation?
@DCdeBrabander
@nmfzone I use Lumen's latest version, 5.3.2. I'll come back on this tomorrow, when I am back at dev-codebase on my local machine at the office 👍 Hopefully I'll figure it out with your example :)
@nmfzone After using your User model (and its db table), setting single testuser and sending following request to /oauth/token ,
--removed image--
I get the following response:
jeej But why do I have to create an user with email and password, seperate from client_id and client_secret? Can't the application create a client_id and secret from these credentials or..?
Also why does the route /oauth/token/refresh say that I'm unauthorized if I use it like https://laravel.com/docs/5.3/passport#refreshing-tokens says I should?
I'm probably coming across as a noob, but implementing oAuth2 is new for me, and I need to do it well xD
@DCdeBrabander The client_id and client_secret are shared for all users. It means client_id and client_secret are "something" to communicate between your application and oauth2. So, It doesn't rely on a specific user.
If you want to create them for the specific user, it's called Personal Access Token. Of course, you can make it.
Hmm, the refresh token will not work because Passport refresh token relies on a session (see here), but Lumen doesn't support session anymore. If you read my code, I make my own implementation ('refresh-token' route). Hope that helps.
Comment updated.
@nmfzone thank you for pointing me (assuming) in the right direction :) I will check Personal Access Token's today then. You think I can realise my wanted implementation via your code https://github.com/TeknikInformatikaUII/insta-dakwah-api/blob/master/app/Http/Controllers/Auth/LoginController.php ?
Btw, if you say IM'ing is better. Just say the word.
p.s. correct you used https://github.com/esbenp/lumen-api-oauth/blob/master/app/Auth/Proxy.php as reference? :D
@DCdeBrabander Wohoo, good catch! :smile: You're right, I use it as a reference. I've used it since the first time I implement OAuth2 in Lumen (5.1).
No no, I will not do that. It's just about time. It just because I implementing the OAuth2 earlier than you.
I don't know you "can" or not. But, if you're having trouble, It has been my pleasure to help you.
@nmfzone Personal Access Token's wont work for my goal, they do not expire.
@DCdeBrabander you can set up a small script to remove them from the db if their date is past a certain timeframe. wouldn't be that hard. then again there are easier solutions. just depends how bad you need it.
@michaelmano @nmfzone I have trouble implementing a process where seperate 'users'/clients can request (and refresh!) tokens and send requests with these (access) tokens. Everything I try in postman (when requesting an 'auth grouped route') returns as 'Unauthorized'. Even when I just 'logged in' (got tokens) and used access token in request. Am I doing something wrong? How do you guys test routes/endpoints where you need oauth2 authentication?
If I want to support multiple users requesting/refreshing tokens, and using these tokens to request 'authed' routes do I still need that cookie implementation? I'm a bit lost what approach I should take and what is missing/need to built seperately in every approach. Do I need Personal Access Tokens (with custom tinkering...) or Password Grant? I don't want a callback-step in de process.
I tried some stuff, but for the sake of helping, assume that I currently have no code/controllers/models myself anymore. @michaelmano I think you are right, I think I need a Password Grant ( officially a client_credentials grant?) and somehow invalidate these tokens myself?
My apologies if I come across as a noob, but I simply have no understanding how to implement OAuth2 in Lumen (and more importantly, what to write myself where needed). Help is much needed, thanks!
p.s. If after all this my understanding of the package and OAuth2 has improved a great deal, I'd like to help maintain these packages. I think Passport should be enabled by default in Lumen.
@DCdeBrabander Do you have clone my repo and try it? I just believe my implementation is what are you want.
I use password_grant (using username and password). Every user can make a request to the application to get their access token. And of course, every user can make a request for the refresh_token.
p.s. Why don't you use my code and forget about "how to" implementing OAuth2 in Lumen. And you just need to start to build your own API.
@nmfzone That would be nice, but the functionality exists mainly in your logincontroller yes? If you could tell me how to call a route which uses the auth middleware (ones that are protected)? In other words, apparantly I have some trouble testing these routes in Postman, can you provide an example of such a call (with params)? If I can test your implementation that would be great. :D (since I can 'login' with your implementation).
And lastly, does your version really works with multiple refresh_tokens? You store them as cookie on server, these are saved seperately every login?
I need to 'know' how everything works, because I have to share the (routes of) API with 'outside' first party (/predefined) clients. So I need to test the (middleware of the) API as such :)
@DCdeBrabander First, Cookies aren't stored in the server. Cookies stored in the client browser. I think you must read first about it. I'm sorry, but It's very fundamental.
Secondly, my implementation isn't using "auth" middleware, but "auth:api" middleware. And all routes under "routes/api.php" are protected "auth:api" by default (look at the bottom bootstrap/app.php). You can see my example "how to access" routes that protected by "auth:api" in the video (look at the bottom Readme.md).
@nmfzone Thanks for clearing that up but do I really need to store the refresh token in cookie at client? Can't I just send them the token and they need to provide it when refreshing access_token? I've seen your video, I tried that and via that approach postman (thus API) says I'm unauthorized.
Same middleware group:
My Controller (at moment of writing almost identical to yours again): http://hastebin.com/zequtemuza.xml
So my login works (getting first tokens) works: --removed image--
But getting that auth route: --removed image--
(note, access_token differs over time because 'I logged in again before sending request to auth route)
Why am I unauthorized, I see access_tokens and refresh_tokens being made in DB?
@DCdeBrabander If you see my video correctly, you won't send Authorization via HTTP params. The authorization is sent in the header.
No, you don't really need to store the refresh_token in cookies, no. It's only my implementation. If you don't need to use cookies, you can remove all code outside the try catch (and remove the try catch too). Because the /oauth/token already gives you a complete response.
But, if you need a cookie, don't forget to install "illuminate/cookie" in your composer.json and setup it. 1 2 3
p.s. If you could show me all your code, maybe I can check then guide you.
@DCdeBrabander If you still get "Unauthorized", what web server are you using? Apache or NginX?
If you use Apache, please take a look at my .htaccess. You MUST add it to your .htaccess. Or you can copy from your Laravel's .htaccess.
But, if you still get unauthorized, hmm I think you aren't setup this package correctly or maybe you forget about some configuration.
@nmfzone thanks for clearing up the authorization header, silly mistake. that in combination with the .htacess-edit did it!! I got a response instead of 'unauthorized'. :D Now I have to test and finish the refresh_token stuff (rather without cookies). I rather want more user-friendly errors when token is not valid anymore and stuff but we are somewhere now.
About the .htaccess, in production we have a nginx-environment maintained by other party. What do I need to give/say to them to make this work in the production environment?
And the cookies, I had all requirements set up, it works but just rather not needed. ;)
@DCdeBrabander haha, good. I'm glad to hear your code works.
If using NginX, you don't need to add special configuration. It works out-of-the-box. Because I use NginX, and I don't need to add special configuration.
@nmfzone Glad to hear NginX will give no trouble :) That means you are almost done with me ;-)
I get errors trying to use the refreshtoken: When using normal params (apparantly wrong): {"error":"invalidrequest","message":"The refresh token is invalid.","hint":"Cannot decrypt the refresh token"} or when using form-data body: _{"error":"invalidrequest","message":"The refresh token is invalid.","hint":"Token is not linked to client"}
I'm calling my /token/refresh endpoint which calls refreshToken() in the controller where only this happens:
return $this->access('refresh_token', [ 'refresh_token' => $request->refresh_token //instead of ->cookie( 'refresh_token' ) ]);
@DCdeBrabander Is your middleware still active? Don't forget to comment the middleware and all code that you don't use.
haha "almost done with me". Whoops, I do not think so. If I have the knowledge about "it", any time you can ask me.
@DCdeBrabander Aaah, I think you use the access_token instead refresh_token, so the passport says like that. Make sure you send "refresh_token", not "access_token".
@nmfzone what do you mean with "Is your middleware still active?" Do you mean Middleware/Authenticate.php?
I use the default auth config:
return [
'defaults' => [
'guard' => 'api',
'passwords' => 'users',
],
'guards' => [
'api' => [
'driver' => 'passport',
'provider' => 'users',
],
],
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => \App\User::class
]
]
];
But for some reason it seems to work now, perhaps I was sending to much data / copy+pasted it wrong. I'll keep you posted. Thanks for all your help though!
@DCdeBrabander No, I mean middleware in "bootstrap/app.php".
Good. You're welcome.
p.s. Don't forget about "clean code" :smile:
@nmfzone you mean the default:
$app->routeMiddleware([ 'auth' => App\Http\Middleware\Authenticate::class, ]);
?
I have only one question left; What is the built-in way (if there is any) to revoke 'old' access tokens after refreshing? When user logs in / refreshes token, I want to revoke all old ones of that user (1 active at a time).
p.s. I won't forget :smile: :+1: All for clean, readable and maintainable code.
@DCdeBrabander No, I mean $app->middleware()
. Forget it if you don't have it in your bootstrap/app.php.
All the access_token has been revoked automatically (by default) if someone requesting a new access token.
@nmfzone I indeed have no $app->middleware()
in my bootstrap/app.php.
I don't hope all access_tokens get revoked (of all users) when someone requests a new one? 😄
My goal of today is to determine / implement a way to differentiate each call from each user. I need to save (for example) user name if they send a request with only the access_token in Authorization header. Is this where the different client_id's come in?
@DCdeBrabander Whoops :laughing: I mean all the access_token for the specific user.
What do you mean? Do you mean how to get the user which make a request?
You only need $request->user()
. $request is Illuminate\Http\Request
.
@nmfzone I know what Illuminate\Http\Request
is, not that noob with Laravel/Lumen. hehe 😄
still need to test multiple 'different' users but I indeed got user information via $request->user()
, only had to use the Auth::user()
in the past.
Also scopes started working (have 2 different type of users, the api 'owner' and rest):
// Define different 'scopes'.
// normally this is used for 'rights' instead of 'usertypes'
\Laravel\Passport\Passport::tokensCan([
'reseller' => 'Reseller Token',
'owner' => 'API Owner'
]);
And reading it via:
$r->user()->tokenCan('owner');
$r->user()->tokenCan('reseller');
Just for information, I implemented the scopes like such, maybe it helps someone:
in a config file, i got the following:
'scopes' =>[
'reseller' => 'Reseller Token',
'owner' => 'API Owner'
]
which I load into
\Laravel\Passport\Passport::tokensCan( config('oauth_client.scopes') );
what should be in line with users table:
$table->enum('type', ['owner','reseller'])->comment('All 3rd party users should be \'reseller\' ');
with an extra check while requesting /login:
if( !in_array($User->type, array_keys(config('oauth_client.scopes'))) ){
return $this->sendIncorrectScopeResponse();
}
and send scope in password grant to an access() or a proxy() method:
'scope' => $User->type
Currently I solved my route-groups as such, better suggestions are welcome (I tried putting it in middleware but didn't get it right..) :
....
if( $request->user()->tokenCan('reseller') ){
$app->group(['middleware'=>['auth:api']], function () use ($app) {
...routes for resellers only...
....
And that's it 😄 Because I have a fallback-route underneath routes file fetching everything what didn't get 'caught' by others it gives a JSON-response for 'invalid' users/tokens that route doesn't exist.
@DCdeBrabander haha, no no, I don't say it :smile: . I mean maybe someone will think about $request with Illuminate\Support\Facades\Request
.
Just my two cents, I think tokenCan()
better used in the controller (in every function) (like Taylor said 'maybe'). Or if you used it in routes, maybe you could make it as a custom middleware.
p.s. I use Bouncer, instead of token scopes.
@nmfzone I don't have the time to use another package to take care of a simple user role 'problem'. But you are right, I changed the routes part to a Middleware class:
$app->routeMiddleware([
'scope' => App\Http\Middleware\Scope::class,
]);
public function handle($request, Closure $next, $scope = null)
{
if( $scope !== '*' && !$request->user()->tokenCan($scope)){
return Response::error(
'You don\'t have the correct permissions to access this endpoint.',
401
);
}
// If request is good, proceed to the requested endpoint
return $next($request);
}
and in routes just;
$app->group(['middleware'=>['auth:api','scope:*']]....
or
$app->group(['middleware'=>['auth:api', 'scope:owner']]....
Much cleaner solution this way. Also needed If you wan't 'correct' response instead everything falling back to a 'route not existing' response.
Does any one give an example how to use passport personal access tokens in lumen? And do we need User Model implementation? Like username and password.
@miansohailanjum usually personal tokens are created by authenticated user himself, manually somewhere on the website. So it's more likely to be used in Laravel than in Lumen (because Lumen by default doesn't have sessions). You don't need username anywhere for access tokens - they rely on user numeric IDs, and password is only needed for password grant (if you are going to have it).
Thanks @dusterio. So what will be the best approach to use if i do not want to use user login and authorization code for my API?
@miansohailanjum it's definitely doable, you just need to implement League\OAuth2\Server\Repositories\UserRepositoryInterface - it's a class (contract) with just one method, and re-bind Laravel Passport to it (you can see example in AccessTokenController.php in this package). Then you can issue your tokens for pseudo users, something internal
Thanks @dusterio. I will give it a try. Hopefully will be able to get through this.
@nmfzone I noticed in your previous post that you mentioned tokens should be automatically revoked when a new one is requested but that is definitely not happening for me. I have over 10 active tokens for my user in my DB now.
@Bpflugrad I don't really remember. But which version you're using? It has been so long, I think at that time I use Passport version 0.2.2.
@nmfzone It appears Lumen Passport brings with it laravel/passport >= 0.2.2.
So I just did a bunch of tests. Route /oauth/token is defined to Dusterio\LumenPassport\Http\Controllers\AccessTokenController@issueToken
. However, when I try to follow the code Laravel\Passport\Http\Controllers\AccessTokenController@issueToken
is what gets called. I added a bunch of logging in places to find this. Dusterio\LumenPassport\Http\Controllers\AccessTokenController@issueToken
has code to revoke old tokens if LumenPassport::$allowMultipleTokens
is false, however since it is never being called this never happens.
@dusterio do you know why this would be? Sorry if this is obvious and I just didn't install everything correctly :(
For those who may come searching for a solution to revoking tokens when a new token is issued I ended up writing a listener that does the job:
<?php
namespace App\Listeners;
use Laravel\Passport\Events\AccessTokenCreated;
use Laravel\Passport\Token;
use Laravel\Passport\Passport;
use Illuminate\Database\Connection;
class RevokeOldTokens
{
/**
* The database connection.
*
* @var \Illuminate\Database\Connection
*/
protected $database;
/**
* Create the event listener.
*
* @param \Illuminate\Database\Connection $database
* @return void
*/
public function __construct(Connection $database)
{
$this->database = $database;
}
/**
* Handle the event.
*
* Will revoke all unrevoked tokens for this userID and clientID, including the associated refresh tokens.
*
* @param AccessTokenCreated $event
* @return void
*/
public function handle(AccessTokenCreated $event)
{
$query = Token::where('user_id', $event->userId)->where('client_id', $event->clientId)->where('id', '<>', $event->tokenId)->where('revoked', false);
$tokens = $query->get();
foreach($tokens as $token) {
if (Passport::$pruneRevokedTokens) {
$this->database->table('oauth_refresh_tokens')
->where('access_token_id', $token->id)->delete();
$token->delete();
} else {
$this->database->table('oauth_refresh_tokens')
->where('access_token_id', $token->id)->update(['revoked' => true]);
$token->update(['revoked' => true]);
$token->save();
}
}
}
}
@Bpflugrad Yea, but >= 0.2.2
doesn't mean Lumen Passport using Laravel Passport version 0.2.2
. But of course, you can tweak it so when the new token is issued, the older version should be revoked.
Hi @dusterio
This looks exactly like what I've been looking for. Was kind of surprised when I saw Passport wasn't working with Lumen out of the box - seems like that would be the first stop for it :)
Anyway, I'm going to try out your package with the latest Lumen install on a production environment. We use a multi node setup, as I like to keep things separate and such. Please let me know if you need any help maintaining this project, code wise or anywhere else.
Thanks again!