Setting up Auth between your Laravel API & the frontend JS client using Passport

Harish Toshniwal • October 10, 2019

original laravel

Laravel Passport is an official Laravel package to implement API authentication in your Laravel apps. It provides a full OAuth2 server implementation for your Laravel applications.

Things we have

What do we want to achieve?

How to achieve that and what will we use?

First, install and configure Passport as suggested in the docs. Next, we need to post the client_id & client_secret along with the user’s email & password to passports /oauth/token route to get the access_token & refresh_token. But there’s a problem! Where do we store the client_id & client_secret ? We cannot store them on the frontend since it will be exposed and won’t be secure. client_id is fine, but we can’t store the client_secret

The solution is we ask the frontend to post only the users email and password and we somehow add the client_id & client_secret to the request on the backend in the API, thereby not exposing them on the frontend. There are 2 ways to do that and I will show you both.

This first approach is not what I would recommend, am showing you this approach since it’s the most common solution available on the internet

First is to use a custom route/endpoint pointing to a new controller. Eg: Route::post(‘/login’, ‘PassportAuthController@login’);. Now, we need to ask our frontend client to post to this new route instead of passports /oauth/token route. In the login() method of the new PassportAuthController we will access the request, add the client_id & client_secret to it and delegate the request to passports /oauth/token route using guzzle and making an http call within the controller method itself. The controller may look like this:

class PassportAuthController
{
    public function login(Request $request)
    {
        $http = new GuzzleHttp\Client;

        try {
            $response = $http->post(config('services.passport.login_endpoint'), [
                'form_params' => [
                    'grant_type' => 'password',
                    'client_id' => config('services.passport.client_id'),
                    'client_secret' => config('services.passport.client_secret'),
                    'username' => $request->email,
                    'password' => $request->password,
                ]
            ]);

            return $response->getBody();
        } catch (BadResponseException $e) {
            if ($e->getCode() === 400) {
                return response()->json('Invalid Request', $e->getCode());
            } elseif ($e->getCode() === 401) {
                return response()->json('Your credentials are incorrect. Please try again', $e->getCode());
            }
        }
    }
}

You can also add a new route for refreshing the token, that will have the same mechanics as the login() method, just the grant_type will be refresh_token and it won’t include the email & password but the refresh_token received from the /login request. The route and controller method might look like this for it:

Route::post(‘/refresh-token’, ‘PassportAuthController@refresh’);

Controller method:

public function refresh(Request $request)
{
    $http = new Client;

    try {
        $response = $http->post(config('services.passport.login_endpoint'), [
            'form_params' => [
                'grant_type' => 'refresh_token',
                'client_id' => config('services.passport.client_id'),
                'client_secret' => config('services.passport.client_secret'),
                'refresh_token' => $request->refreshToken,
            ]
        ]);

        return $response->getBody();
    } catch (BadResponseException $e) {
        if ($e->getCode() === 400) {
            return response()->json('Invalid Request', $e->getCode());
        } elseif ($e->getCode() === 401) {
            return response()->json('Your refresh token is incorrect. Please try again', $e->getCode());
        }
    }
}

The problem with this approach is that it makes an additional http call which isn’t technically needed. I showed you this approach since it’s the most common solution available on the internet. The second approach which I would suggest to use and what we are using at Jogg is as follows:

class PassportAuthController extends AccessTokenController
{
    public function __construct(AuthorizationServer $server,
                                TokenRepository $tokens,
                                JwtParser $jwt)
    {
        parent::__construct($server, $tokens, $jwt);
    }

    public function login(ServerRequestInterface $request)
    {
        $parsedBody = $request->getParsedBody();

        $parsedBody['grant_type'] = 'password';
        $parsedBody['client_id'] = config('services.passport.client_id');
        $parsedBody['client_secret'] = config('services.passport.client_secret');

        return parent::issueToken($request->withParsedBody($parsedBody));
    }
}

This way we no longer need to make an extra http call in our code.

You can use the following code to implement the logout functionality:

public function logout()
{
    auth()->user()->tokens->each->delete();

    return response()->json('Logged out', 200);
}

Two most important points:

1: Remember, to make the logout endpoint protected using passports auth:api middleware

2: Since, we will be storing the access & refresh token in an http only cookie to pass it along with every request in the Authorization header, please have proper CORS in place to protect your API against CSRF attacks. We recommend using Barry vd. Heuvel’s laravel-cors package to implement that

That’s all, feel free to let me know your thoughts about this on my twitter.