Laravel Passport Password Grant with Custom User Providers

Matt Hue
Matt Hue
Feb 10 · 3 min read

We have been using Laravel Passport for authenticating API’s and first party applications such as single page apps (SPA)and native mobile apps and so far, the experience have been good. Passport is an OAuth2.0 implementation which works really well with Laravel’s default auth mechanism.

There are different types of oauth implementation which depends on the type of application being built. For SPA applications, we use implicit grant type and for first party mobile apps, we use password type. [More on OAuth grant types].

Everything has been smooth sailing until the client wants to add ‘a little` change to the authentication method. It turns out, the little change is not so little after all.

Basically, the client has an existing app written in .NET and the entire project involves redesigning the app, adding more features, create android/ios apps and rewrite the server into Laravel. Little did we know that his existing .NET app is authenticating users to a third party provider using non-standard compliance auth mechanism. No oauth, no saml, no open-connect-id, no nothing. Authenticating users is done via a ‘restful’ api mechanism.

So we figured, yeah its fine, I’m sure Passport + Laravel already had this figured out.

Enter Laravel Custom Auth Providers

Laravel comes with very convenient auth mechanism and scaffolding out of the box but it also lets you provide your own implementation. So we implemented our own custom auth provider which we will connect to the .NET rest api for authentication.

// Custom Auth Provider...
public function retrieveByCredentials(array $credentials)
{
if (empty($credentials) ||
(count($credentials) === 1 &&
array_key_exists('password', $credentials))) {
return;
}
if (! array_key_exists('password', $credentials)) {
// query existing db if user exists
$user = User::whereEmail($credentials['email']);
// if not, query crm server
if (!$user->first()) {
return $this- >validateEmailIsActiveUser($credentials['email']);
}
return $user->first();
}
$apiResponse = $this->api->login(
$credentials['email'],
$credentials['password']
);
$user = json_decode($apiResponse->getBody()); if ($user && $user->UserLoginResult) {
return $this->persistUser($user->UserLoginResult);
}
return null;
}
...
Other contracts are implemented separately...
public function retrieveById($identifier);
public function retrieveByToken($identifier, $token);
public function updateRememberToken(Authenticatable $user, $token);
public function validateCredentials(Authenticatable $user, array $credentials);

So every time the user logs in to the application, it does api call to an external server with the user entered username and password.

It was all good after that, I thought. Then I went and installed passport and try to login.

HTTP 404. Model Not Found Error.

What? We already implemented the custom user auth and registered it in the providers. It turns out, passport does not care if you have custom auth providers installed. It doesn’t use these methods for authenticating users. Instead, it just assumes the default Laravel user auth mechanism.

Luckily for us, Passport has got us covered!

Enter `validateForPassportPasswordGrant` and `findForPassport` grant user model methods.

It turns out, when I looked into Passport repositories file, it tries to check if user model has the methods above and if it exist, it resolves the user from the returned values of the methods above.

So inside our user model,

public function validateForPassportPasswordGrant($password)
{
return CustomUserProvider::retrieveByCredentials([
'email' => $this->email,
'password' => $password,
]);
}
public function findForPassport($email)
{
return CustomUserProvider::retrieveByCredentials([
'email' => $email,
]);
}

Now we have our session auth and passport auth mechanisms using the same user provider methods.

Problem solved!