User registration for API/SPA

1.8k views Asked by At

I am creating an API and a separate front-end app that will consume said API. In my particular case I'm using Laravel Passport for my API and some VueJS for my frontend app.

In order for a user to create an account, a user must POST to a route (/oauth/token) on the API which, requires a client_secret to be passed (https://laravel.com/docs/5.3/passport#password-grant-tokens).

The only options I see are:

  1. Having the client_secret sent as a header from my frontend app. However, putting this token out in the open doesn't seem smart.
  2. Don't require the client_secret at all. This doesn't seem much better than option 1.
  3. Have a dynamic page on my frontend app that can securely store the client_secret and then send it to the API. While this is obviously the most secure, it seems to partially defeat the purpose of a fully static frontend (SPA).

What's the best practice for this type of approach? I've searched for how this is dealt with in general with an API and SPA, but I haven't found anything that points me in the right direction.

3

There are 3 answers

5
Hammerbot On

I came across the same problem, and I didn't find much more documentation on the problem.

So here is what I did, that seems working great so far, you'll tell me if you see anything wrong.

For my apps, I'll be using password grant clients that I create on the fly for each "client" of my app. By client I mean browser, or mobile app, or anything.

Each browser, checks at startup if they have any client_id and client_secret into localStorage (or cookies, or anything). Then, if they don't, they call an endpoint of your API that will create a password grant client and return the information to the browser.

The browser will then be able to login the user using this new client information and his credentials.

Here is the controller I use to create a password grant client:

<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use Illuminate\Contracts\Hashing\Hasher;
use Illuminate\Http\Request;
use Laravel\Passport\ClientRepository;

class AuthController extends Controller
{
    protected $hasher;

    protected $clients;

    public function __construct (Hasher $hasher, ClientRepository $clients)
    {
        $this->hasher = $hasher;
        $this->clients = $clients;
    }

    public function makeClient (Request $request)
    {
        $client = $this->clients->create(null,$request->header('User-Agent','Unknown Device'), '', false, true);

        return $client->makeVisible('secret');
    }
}

As you can see, as the name for the client, I try to store the User-Agent of the browser. So I can potentially display a page to my user with all his clients and giving him the right to revoke some clients like:

"Google Chrome, New York". You can also store the client IP or anything in there that will help you identify more precisely the client type of device...

3
Spomky-Labs On

From my point of view, the Laravel Passport component seems to implement the OAuth2 Framework Protocol incorrectly.

The client_id and client_secret parameters are not part of the grant type. For the Resource Owner Password Credentials grant type, the required parameters are username and password (see RFC6749 section 4.3.2).

client_id and client_secret are used to authenticate a confidential client that sends its credentials through the body parameters (see RFC6749 section 2.3.1). The Laravel Passport component should allow other client authentication schemes (especially the HTTP Basic Authentication Scheme). The RFC6749 also indicates that

Including the client credentials in the request-body using the two parameters is NOT RECOMMENDED and SHOULD be limited to clients unable to directly utilize the HTTP Basic authentication scheme

The OpenID Connect Core specification lists some of those schemes in its section 9. The RFC6749 does not indicates how public clients (e.g. SPA) should authenticate against the token endpoint. They are supposed to use the Implicit grant type which does not require a client authentication.

Anyway, a solution could be to use a kind of proxy. This proxy has to be installed on a server. It will receive all requests from the SPA (without client secret), add the client secret and transmit the modified request to the Laravel Passport endpoint. Then the response is sent to the SPA. This way the SPA never exposes the client secret.

1
yan_n On

The simpler way would be to take care of the user registration with the Laravel app running Passport itself (and not with the frontend Vuejs app via API).
Once the user is registered and logged in, you can use Passport's CreateFreshApiToken middleware to add a token to the user's cookie while loading up your frontend app. No more problem with client_secret.

See https://laravel.com/docs/5.3/passport#consuming-your-api-with-javascript and https://mattstauffer.co/blog/introducing-laravel-passport#super-powered-access-to-the-api-for-frontend-views

Also oauth/token doesn't create a user I believe? It is supposed to deliver a token (for password grant client) or an authorization code (authorization code grant client).