29
votes

I'm using Laravel Passport to give access to some parts of my API to third-party apps.

But, I also use my own API through my own first-party Native Android App. So, I looked over the whole internet for the best practice in this case, but getting stuck to get to a conclusion.

Here are the possibilities I found:

Possibility #01

I can follow the User Credential Password Grant Flow.
In this case I need to pass a client_secret and client_id to the authorization server. In order to keep them safe I can't write them in my mobile application's source code (APKs are decompilable...).

So, I have 2 choices.

Possibility #01 - Choice A

Proxyfying through my own server and inject the secret before calling the oauth endpoint:

$proxy = Request::create('/oauth/token', 'post', [
    'grant_type' => 'password',
    'client_id' => 1,
    'client_secret' => 'myownclientsecretishere',
    'username' => $username,
    'password' => $password
]);
$proxy->headers->set('Accept', 'application/json');
$response = app()->handle($proxy);

Possibility #01 - Choice B

Inject the secret when calling the oauth endpoint using a Middleware:

class InjectPasswordGrantSecret
{
    public function handle($request, Closure $next)
    {
        $request->request->add([
            'client_id' => 1,
            'client_secret' => 'myownclientsecretishere'
        ]);
        return $next($request);
    }
}

These are working examples, but they are also greedy in resources.. I tried to use Apache benchmark on my local machine and I got something like 9 requests/second.

Possibility #02

I can follow the Personal Access Grant.
This one doesn't look like a standard in OAuth2, it allows us to create a token through any custom route, just like this:

if (! auth()->attempt(compact('username', 'password'))) {
    return error_response(__('auth.failed'));
}
$user = auth()->user();
$token = $user->createToken(null)->accessToken;

Using Apache benchmark I get better result (something like 30 requests/second).

But, token lifetime is not configurable by default and is set to 1 year (note that there are workarounds to get this lifetime configurable using a Custom Provider).

I am really wondering if this solution is meant to be used in a production environment.

Initially, I used JWT tymon library because I only had my own app. But now that I need to get it to work with first-party AND third-party apps, I thought that OAuth2 (through Laravel Passport) would be a good solution...

I hope someone can help me out with this, and explain what could be a good solution to get it to work securely and [not slowly] on production servers.

2
Have you tried laravel.com/docs/5.7/passport#token-lifetimes? I think it applies on personal and password grant tokens. - Ali Farhoudi
@AliFarhoudi Like I said (your link confirm this). Personal Access Grant lifetime is configurable using a Custom Provider (workaround). It's the reason why I'm not sure I should use this for my own Android App in production. - Marc
There's nothing to be worried about a custom provider. When framework hasn't consider that, you can implement that in your custom way. - Ali Farhoudi
@Marc In that case I'd say upgrade to 6 if possible, so you can just use Passport::personalAccessTokensExpireIn in AuthServiceProvider. - Wouter Florijn

2 Answers

2
votes

Here is the page that I always refer: https://oauth2.thephpleague.com/authorization-server/which-grant/ enter image description here

It says

We strongly recommend that you use the Authorization Code flow over the Password grant for several reasons. We eliminate the password grant option.

Then, it says clearly in the diagram that you should use

Authorization Code Grant with PKCE

and also indicates that

If the client is a web application that has runs entirely on the front end (e.g. a single page web application) or a native application such as a mobile app you should implement the authorization code grant with the PKCE extension. You can read further in the document.

Additionally, there is a good tutorial here explaining every detail of the flow with an example: https://auth0.com/docs/architecture-scenarios/mobile-api

I hope these help.

PS: When the time I needed to authorize my users in my first party application, I had used password grant by referencing this very chart. However, it seems like it changed and password grant is now not a best practice anymore and is not recommended.

0
votes

What I've done before is implemented my own controller that extends the Laravel\Passport\Http\Controllers\AccessTokenController. Not only can you set this controller up to handle creating the tokens, you can add in routes for refreshing and revoking the tokens, as well.

For example, I had a create method for showing the login form, a store method for creating the access token, a refresh method for using the refresh token to refresh expired access tokens, and a revoke method for revoking the provided access token and related refresh token.

I can't provide exact code since the project is not open source, but this is a quick example controller with just the store method:

use Illuminate\Http\Request;
use Psr\Http\Message\ServerRequestInterface;
use Laravel\Passport\Http\Controllers\AccessTokenController;

class MyAccessTokenController extends AccessTokenController
{
    public function store(Request $request, ServerRequestInterface $tokenRequest)
    {
        // update the request to force your grant type, id, secret, and scopes
        $request->request->add([
            'grant_type' => 'password',
            'client_id' => 'your-client-id',
            'client_secret' => 'your-client-secret',
            'scope' => 'your-desired-scopes',
        ]);

        // generate the token
        $token = $this->issueToken($tokenRequest->withParsedBody($request->request->all()));

        // make sure it was successful
        if ($token->status() != 200) {
            // handle error
        }

        // return the token
        return $token;
    }
}

The refresh method would basically be the same, but the grant_type would be refresh_token.

The revoke method would get the authed user's token ($token = $request->user()->token(), revoke it ($token->revoke()), and then manually go through the oauth_refresh_tokens table for the related tokens (access_token_id = $token->id) and update the revoked field to true.

Create some routes to use your custom controller and you're good to go (NB: the revoke route will need the auth:api middleware).