8

I'm creating a RESTful API using Lumen and would like to add HTTP basic Authentication for security.

On the routes.php file, it set the auth.basic middle for every routes:

$app->get('profile', ['middleware' => 'auth.basic', function() {
     // logic here
}]);

Now when I access http://example-api.local/profile I am now prompted with the HTTP basic authentication, which is good. But when I try to login, I get this error message: Fatal error: Class '\App\User' not found in C:\..\vendor\illuminate\auth\EloquentUserProvider.php on line 126

I do not want the validation of users to be done on a database since I will just have one credential so most likely it will just get the username and password on a variable and validate it from there.

Btw, I reference it thru this laracast tutorial. Though it is a Laravel app tutorial, I am implementing it on Lumen app.

basagabi
  • 4,900
  • 6
  • 38
  • 84

3 Answers3

19

I am answering my own question as I was able to make it work but would still like to know more insights from others regarding my solution and the proper laravel way of doing it.

I was able to work on this by creating a custom middleware that does this:

<?php

namespace App\Http\Middleware;

use Closure;

class HttpBasicAuth
{

    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        $envs = [
            'staging',
            'production'
        ];

        if(in_array(app()->environment(), $envs)) {
            if($request->getUser() != env('API_USERNAME') || $request->getPassword() != env('API_PASSWORD')) {
                $headers = array('WWW-Authenticate' => 'Basic');
                return response('Unauthorized', 401, $headers);
            }
        }

        return $next($request);
    }

}

If you'll look into the code, it is pretty basic and works well. Though I am wondering if there is a "Laravel" way of doing this as the code above is a plain PHP code that does HTTP basic authentication.

If you'll notice, validation of username and password is hard coded on the .env file as I do not see the need for database access for validation.

funerr
  • 7,212
  • 14
  • 81
  • 129
basagabi
  • 4,900
  • 6
  • 38
  • 84
  • 4
    This worked great. Only issue (which is very important) is that this line: `if($request->getUser() != env('API_USERNAME') && $request->getPassword() != env('API_PASSWORD')) {` should be `if($request->getUser() != env('API_USERNAME') || $request->getPassword() != env('API_PASSWORD')) {` – note the `||` vs `&&`. – Alex Coleman Apr 07 '16 at 18:58
  • 1
    Very nice simple solution. One way to clean it up even more would be to change the environment condition to this: `app()->environment('production', 'staging')` – Taylor Apr 11 '17 at 15:50
2

Check your bootstrap/app.php. Make sure you have registered your auth.basic middleware, something like this:

$app->routeMiddleware([
    'auth.basic' => Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
]);

After that, change your routes:

$app->get('/profile', ['middleware' => 'auth.basic', function() {
    // Logic
}]);

EDIT

If you want to use database instead of eloquent authentication, you may call:

Auth::setDefaultDriver('database');

Before you attempt to authenticate:

Auth::attempt([
    'email' => 'info@foo.bar',
    'password' => 'secret',
]);

Edit #2

If you wish to authenticate in hardcode ways, you may define your own driver for AuthManager class:

Auth::setDefaultDriver('basic');

Auth::extend('basic', function () {
    return new App\Auth\Basic();
});

And then below is the basic of App\Auth\Basic class:

<?php

namespace App\Auth;

use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Contracts\Auth\Authenticatable;

class Basic implements UserProvider
{
    /**
     * Retrieve a user by their unique identifier.
     *
     * @param  mixed  $identifier
     * @return \Illuminate\Contracts\Auth\Authenticatable|null
     */
    public function retrieveById($identifier)
    {

    }

    /**
     * Retrieve a user by their unique identifier and "remember me" token.
     *
     * @param  mixed   $identifier
     * @param  string  $token
     * @return \Illuminate\Contracts\Auth\Authenticatable|null
     */
    public function retrieveByToken($identifier, $token)
    {

    }

    /**
     * Update the "remember me" token for the given user in storage.
     *
     * @param  \Illuminate\Contracts\Auth\Authenticatable  $user
     * @param  string  $token
     * @return void
     */
    public function updateRememberToken(Authenticatable $user, $token)
    {

    }

    /**
     * Retrieve a user by the given credentials.
     *
     * @param  array  $credentials
     * @return \Illuminate\Contracts\Auth\Authenticatable|null
     */
    public function retrieveByCredentials(array $credentials)
    {
        return new User($credentials);
    }

    /**
     * Validate a user against the given credentials.
     *
     * @param  \Illuminate\Contracts\Auth\Authenticatable  $user
     * @param  array  $credentials
     * @return bool
     */
    public function validateCredentials(Authenticatable $user, array $credentials)
    {
        $identifier = $user->getAuthIdentifier();
        $password = $user->getAuthPassword();

        return ($identifier === 'info@foobarinc.com' && $password === 'password');
    }
}

Note that validateCredentials method needs first argument is an implementation of Illuminate\Contracts\Auth\Authenticatable interface, so you need to create you own User class:

<?php

namespace App\Auth;

use Illuminate\Support\Fluent;
use Illuminate\Contracts\Auth\Authenticatable;

class User extends Fluent implements Authenticatable
{
    /**
     * Get the unique identifier for the user.
     *
     * @return mixed
     */
    public function getAuthIdentifier()
    {
        return $this->email;
    }

    /**
     * Get the password for the user.
     *
     * @return string
     */
    public function getAuthPassword()
    {
        return $this->password;
    }

    /**
     * Get the token value for the "remember me" session.
     *
     * @return string
     */
    public function getRememberToken()
    {

    }

    /**
     * Set the token value for the "remember me" session.
     *
     * @param  string  $value
     * @return void
     */
    public function setRememberToken($value)
    {

    }

    /**
     * Get the column name for the "remember me" token.
     *
     * @return string
     */
    public function getRememberTokenName()
    {

    }
}

And you may test your own driver via Auth::attempt method:

Auth::setDefaultDriver('basic');

Auth::extend('basic', function () {
    return new App\Auth\Basic();
});

dd(Auth::attempt([
    'email' => 'info@foobarinc.com',
    'password' => 'password',
])); // return true
Sebastien C.
  • 4,649
  • 1
  • 21
  • 32
krisanalfa
  • 6,268
  • 2
  • 29
  • 38
  • I have no problem with the auth middleware loading, the problem is that it is validating users from the users table. I do not want it to validated authentication from the database since it will only have one credential for validating and connecting to the database with only one record seems point less to me. So why not just make a `$user == 'John'` instead? I was able to do this with PHP's HTTP authentication but I want to know if there is a "Laravel" way of doing this. – basagabi Dec 25 '15 at 04:07
  • Just got it working with Laravel 5.8. I needed to use `Auth::provider` instead of `Auth::extend` though. And the `Authenticatable` interface also needs a `getAuthIdentifierName` method (empty seems to work). – Sebastien C. Jun 11 '19 at 02:41
-1

First we will extend AuthenticateWithBasicAuth in our middleware.

<?php

 namespace App\Http\Middleware;
 use \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth;


 class HttpBasicAuth extends AuthenticateWithBasicAuth
 {

 }

In config/auth.php create custom guard and we will use custom_http_guard with HttpBasicAuth.

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

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

    'custom_http_guard' => [
        'driver' => 'token',
        'provider' => 'custom_http_provider',
    ],
],

We will use Laravel's default 'token' driver.

'providers' => [
    'users' => [
        'driver' => 'eloquent',
        'model' => App\User::class,
    ],

 'custom_http_provider' => [
     'data' => [
         'email' => 'info@foo.bar',
         'password' => 'secret',
      ]
 ],
],

If you can find the way to return data like above.Then you rock and get code following laravel standards.

Hope you got the idea! Looking for final solution. If someone can complete :)

Vaibhav Arora

Vaibhav
  • 858
  • 10
  • 13