Laravel API verification/protection on subsequent requests: no login / logout and no "users" table

Viewed 956

TLDR; see image below 3 - is that possible and how?


I read about API protection - Sanctum & Passport, but none of these seems what I can accomplish with my app since it's a little specific and simplified in a way.

For example, Sanctum's way of authenticating sounds like something I'd like, but without the /login part (i have a custom /auth part, see below.): https://laravel.com/docs/8.x/sanctum#spa-authenticating.

If the login request is successful, you will be authenticated and subsequent requests to your API routes will automatically be authenticated via the session cookie that the Laravel backend issued to your client.

My app has no login per se - we log-in the user if they have a specified cookie token verified by the 3rd party API (i know token-auth is not the best way to go, but it is quite a specific application/use). It's on /auth, so Sanctum's description above could work, I guess if I knew where to fiddle with it. Our logic:

  1. VueJS: a mobile device sends an encrypted cookie token - app reads it in JS, sends it to my Laravel API for verification.
  2. Get the token in Laravel API, decrypt, send to 2nd API (not in my control), verifying the token, and sends back an OK or NOT OK response with some data.
  3. If the response was OK, the user is "logged-in."
  4. The user can navigate the app, and additional API responses occur - how do I verify it's him and not an imposter or some1 accessing the API directly in the browser?

I guess the session could work for that, but it's my 1st time using Laravel, and nothing seemed to work as expected. Also, sessions stored in files or DB are not something I'm looking forward to if required.

For example, I tried setting a simple session parameter when step 3 above happened and sending it back, but the session store was not set up, yet it seemed at that point. Then I could check that session value to make sure he's the same user that was just verified.

For an easier understanding of what I'm trying to accomplish and if it's even feasible: enter image description here

The main question is, what is the easiest way to have basic API protection/authentication/verification whilst sending the token for authentication to 3rd party API only on 1st request (and if the app is reopened/refreshed of course) - keeping in mind, that no actual users exist on my Laravel API.

Or would it be best to do the token-auth to the 3rd party API on each request?

1 Answers

If I understand your case correctly there's no real User model involved, right? If so, you'll not be able to use any of Laravel's built-in authentication methods as they all rely on the existence of such a model.

In that case you'll need one endpoint and a custom authentication Middleware that you'll need to create yourself in Laravel in order to handle everything:

The endpoint definition:

Route::post('authenticate', [TokenController::class, 'login']);

The controller:

class TokenController extends Controller 
{
    public function login(Request $request)
    {
        // First read the token and decrypt it.
        // Here you'll need to replace "some_decryption()" with the required decrypter based on how your VueJS app encrypts the token.
        $token = some_decryption( $request->input('token') );

        // Then make the request to the verification API, for example using Guzzle.
        $isTokenOk = Http::post('http://your-endpoint.net', [
            'token' => $token,
        ])->successful();

        // Now issue a Laravel API token only if the verification succeeded.
        if (! $isTokenOk) {
            abort(400, 'Verification failed');
        }

        // In order to not store any token in a database, I've chosen something arbitrary and reversibly encrypted.
        return response()->json([
            'api-token' => Crypt::encrypt('authenticated'),
        ]);
    }
}

Subsequent requests should pass the api token in the Authorization header as a Bearer token. And then in the Middleware you'll check for Bearer token and check if it matches our expected value:

class AuthTokenAuthenticationMiddleware
{
    public function handle($request, Closure $next)
    {
        $authToken = $request->bearerToken();

        if (! $authToken || ! Crypt::decrypt($authToken) === 'authenticated') {
            abort(401, 'Unauthenticated');
        }

        return $next($request);
    }
}

The Middleware needs to be registered in app/Http/Kernel.php:

protected $routeMiddleware = [
    ...
    'auth-token' => AuthTokenAuthenticationMiddleware::class,
];

And finally apply this new middleware to any of your routes that should be authenticated:

Route::middleware('auth-token')->get('some/api/route', SomeController::class);

Warning: this authentication mechanism relies on reversible encryption. Anyone able to decrypt or in possession of your APP_KEY will ultimately be able to access your protected endpoints!

Of course this is one way to deal with custom userless authentication and there are many more. You could for example insert an expiration date in the encrypted token instead of the string "authenticated" and verify if it's expired in the middleware. But you get the gist of the steps to be followed...


If you do have a User model in place, then you could use Laravel Sanctum and issue an API token after User retrieval instead of forging a custom encrypted token. See https://laravel.com/docs/8.x/sanctum#issuing-mobile-api-tokens

// Fetch the corresponding user...
$user = User::where('token', $request->input('token'))->first();

return $user->createToken('vuejs_app')->plainTextToken;

Subsequent requests should pass the token in the Authorization header as a Bearer token.

Protect routes using the middleware provided by Sanctum:

Route::middleware('auth:sanctum')->get('some/api/route', SomeController::class);
Related