Search code examples
laravelapacheauthenticationlaravel-passportraspberry-pi4

Laravel API Endpoint "401 Unauthorized" on Server But Works Fine On Localhost


Background

I have developed a React app that uses Laravel as an API. I have added logins via Passport and have been using the Personal Access Client approach quite successfully. I can add new users and tokens, I can revoke tokens, I can reset passwords... All API calls (except login and register) are guarded by the API middleware and it works. If I remove the Bearer ${token} from the header on any of these calls it returns 401 unauthenticated due to the ->middleware('auth:api') wrapper.

The Problem

Everything works completely as expected... until I move everything to my Raspberry Pi server. As soon as I moved everything, the problem began. I can login and I can register, but as soon as I use the new bearer token (that I received from my login or register call(s)) on any of the endpoint calls that follow in my flow, it fails with 401 unauthenticated, immediately. I ran the php artisan passport:client --personal command and successfully entered the id and secret into my .env file as usual. I installed all the composer and vendor packages. I installed all passport package(s) and CLI commands.

It only fails on calls that use the auth middleware.

I have done some digging and it seems the only change I can find (significantly) is that the Pi runs a 32 bit PHP where my localhost runs a 64 bit PHP. Other than that its the same code, DB, versions of Laravel and PHP, everything.

I have tried using the command php artisan passport:client --personal --name="app-name" --redirect_uri="http://192.168.1.1/" which puts a record in the "oauth_clients" table but shows the redirect as http://localhost/. I then try to use SQL to change the value of the column named "redirect" to http://localhost/, manually... but again the change does nothing. Calls still return 401 unauthenticated.

The only other things I can find that might be an issue are:

  1. The fact that all tokens in the database table "oauth_access_tokens", under the column called "redirect", are created with the redirect_uri of http://localhost. No matter what I do it's always localhost and not my servers domain or IP address (which is concerning). Manually changing SQL as I said does nothing but I know Laravel uses a few "read-only" columns for auth so I wonder if this is one of them... perhaps personal access tokens only work on localhost?
  2. My email_verified_at column in my "users" table (generated by passport commands) is null because I was not able to setup the "forgot my password" flow of Passport on localhost since emails won't send out on localhost.

What I have setup is this:

public function boot()
{
    $this->registerPolicies();

    Passport::pruneRevokedTokens();
    Passport::tokensExpireIn(Carbon::now()->addDays(1));
    Passport::refreshTokensExpireIn(Carbon::now()->addDays(14));
    Passport::personalAccessTokensExpireIn(Carbon::now()->addDays(1));
}

AuthServiceProvider Class

public function register(Request $request) {
    $validatedData = $request->validate([
        'image_url' => 'required',
        'last_name' => 'required|max:55',
        'image_url' => 'required|max:250',
        'first_name' => 'required|max:55',
        'password' => 'required|confirmed',
        'email' => 'email|required|unique:users',
    ]);

    $validatedData['password'] = bcrypt($request->password);


    if ($request->hasFile('image_url')) {
        $imageFile = $request->file('image_url');
        $imageExtension = $imageFile->extension();

        if (strtolower($imageExtension) === 'png' || strtolower($imageExtension) === 'jpg') {
            $validatedData['image_url'] = Storage::url( $request->file('image_url')->store('user_pics', 'public') );
        }
    
        $user = User::create($validatedData);
        
        date_default_timezone_set('UTC');
        $date = new \DateTime( date('Y-m-d H:i:s') );
        $user->email_verified_at = $date->format('c');

        $accessToken = $user->createToken('authToken-'.$user->id, ['*'])->accessToken;

        return response([ 'user' => $user, 'access_token' => $accessToken ]);

    } else {
        abort(404, 'Cannot register user without a user image!');
    }
}


public function login(Request $request) {
    $loginData = $request->validate([
        'email' => 'email|required',
        'password' => 'required'
    ]);

    if (!auth()->attempt($loginData)) {
        return response()->json(['statusText' => 'Unauthorized'], 401);
    }

    $user = auth()->user();
    $accessToken = auth()->user()->createToken('authToken-'.$user->id, ['*'])->accessToken;

    return response([ 'user' => $user, 'access_token' => $accessToken ]);
}


public function logout(Request $request) {
    if (auth()->guard('api')->check()) {
        auth()->guard('api')->user()->OauthAcessToken()->delete();

        return response()->json([ 'msg' => 'Successfully logged out!' ]);
    
    } else {
        return abort(404, 'Must be logged in to log a user out');
    }
}


public function refreshToken(Request $request) {
    if (auth()->guard('api')->check()) {
        $user = auth()->user();
        $accessToken = auth()->user()->createToken('authToken-'.$user->id, ['*'])->accessToken;

        return response([ 'user' => $user, 'access_token' => $accessToken ]);
    
    } else {
        return abort(404, 'Must be logged in to refresh a token!');
    }
}

AuthController Class

'defaults' => [
    'guard' => 'web',
    'passwords' => 'users',
],

'guards' => [
    'web' => [
        'driver' => 'session',
        'provider' => 'users',
    ],

    'api' => [
        'driver' => 'passport',
        'provider' => 'users'
    ],
],

config/Auth.php

APP_NAME=MyName
APP_ENV=dev
APP_DEBUG=true
APP_URL=http://192.168.1.1
PASSPORT_PERSONAL_ACCESS_CLIENT_ID="1"
PASSPORT_PERSONAL_ACCESS_CLIENT_SECRET="[SOME LONG HASH]"

.env File


Solution

  • Finally solved it!!

    Turns out it was Apache on the Raspberry Pi server blocking the Authorization header. This finally unblocked me and solved my issues.

    For anyone else coming from a Google search, you can go into your /etc/apache2/apache2.conf file and at the very bottom, paste:

    SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1

    I am using a Raspberry Pi 4 with 32 bit PHP and Apache2.

    Also, I didn't mention in my post that I have been using the following for my apache server root htaccess:

    # Handle Authorization Header
    RewriteCond %{HTTP:Authorization} .
    RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
    

    .htaccess file, server root