ahoi's avatar
Level 5

How to use Passport with PKCE and a VueJS-Frontend

Hello there,

I got a problem on using Passport to protect my api routes.

A bit of context

I am using Laravel Passport to:

  • secure my API routes
  • let my VueJS-frontend access the routes and authenticate different users
  • grant access to those routes for other Laravel-projects

I will show you what I am doing at the moment:

  1. Install Laravel and it's deps
  2. Migrate
  3. Install passport using php artisan passport:install
  4. Add VUE_CLIENT_ID=2 and VUE_CLIENT_SECRET=the secret shown after running passport:install to .env
  5. Add bearer-token to header in my vuejs-app's main.js:
let bearer = Cookies.get ('access_token');

if (bearer) {
	window.axios.defaults.headers.common['Authorization'] = bearer
}

Now I can access the protected API-endpoints using Vue after logging in using this login-method (vue):

 async signIn ({dispatch, commit}, payload) {
        let actionUrl = '/api/auth/login';
        let remember = payload.remember ? payload.remember : false;
        let data = {
            'email': payload.email,
            'secret': payload.secret
        }
        commit ('authRequest');
        return new Promise ((resolve, reject) => {

            commit ('authRequest');
            axios.post (actionUrl, data)
            .then ((resp) => {
                let access_token = 'Bearer ' + resp.data.access_token;
                Cookies.set ('access_token', access_token, {expires: remember ? 365 : 1});
                axios.defaults.headers.common['Authorization'] = access_token;
                commit ('authSuccess', access_token);
                this.dispatch ('user/userRequest', {root: true});
                resolve (access_token);
            })
            .catch ((err) => {
                commit ('authError', err.response.data);
                Cookies.remove ('access_token');
                reject (err);
            })
        })
    },

Which calls this method in LoginController.php:

public function login(Request $request)
{
    $this->validate($request, $this->rules(),
        $this->validationErrorMessages());

    $request = Request::create('/oauth/token', 'POST', [
        'grant_type'    => 'password',
        'client_id'     => config('services.vue_client.id'),
        'client_secret' => config('services.vue_client.secret'),
        'username'      => $request->email,
        'password'      => $request->secret,
    ]);

    return app()->handle($request);
}

This works fine, but ...

The problem

Now @martinbean stated (https://laracasts.com/discuss/channels/testing/test-passport-login) that my way of using Passport is not correct.

Passport is an OAuth server implementation. So either use an appropriate OAuth grant type where you don’t expose your client’s secret key, or use Sanctum instead which is literally built for authentication things like Vue front-ends. If you wish to stick with Passport, then the most appropriate grant type would be authorization code grant with PKCE: The Authorization Code grant with "Proof Key for Code Exchange" (PKCE) is a secure way to authenticate single page applications or native applications to access your API.

So what I learned here:

  • What I did was stupid ;-)
  • I would need to implement PKCE

My questions

  1. Is this correct? If I understand it correctly, PKCE can not be used without redirecting the user to the external auth-screen Passport provides as Vue-components.
  2. Are there good examples on how to use PKCE in a Laravel-project that uses VueJS as frontend?
0 likes
0 replies

Please or to participate in this conversation.