I want to add other fields in password grant login credentials besides username and password, for example, Captcha, two-step-verification.
How to do that
thx
If you create a new service provider (using App\Providers\PassportServiceProvider as an example)
You can then enable a custom grant type.
I'm running a few custom grant types, these are just extending the League\OAuth2\Server\Grant\PasswordGrant and overriding the validateUser method with your own checks.
@ganey could you give me an example of your custom grant ?
i'm currently facing the same issue,
in short i have a running application that i want to give passport/oauth ability.
the problem is when user logging in, it uses a different method of password checking (it uses combination of salt etc.). i could make it work for normal login.
as far as i know laravel passport uses hash->check for user password, this resulting in invalid_credentials.
i've setup a custom providers as you said, but it stuck in Argument 1 passed to App\Providers\PassportServiceProvider::__construct() must be an instance of League\OAuth2\Server\Repositories\UserRepositoryInterface, instance of Illuminate\Foundation\Application given
*in construct function i've added UserRepositoryInterface
let me know how yours work.
There's probably a better way of doing this now but I extended the PassportServiceProvider and copied the registerAuthorizationServer function so that I could register my own grant type.
Swap out the provider in config\app.php with your new one:
'providers' => [
//Laravel\Passport\PassportServiceProvider::class,
App\Providers\PassportClientCredentialsServiceProvider::class,
Updated registerAuthorizationServer function that includes new grant option:
protected function registerAuthorizationServer()
{
parent::registerAuthorizationServer();
$this->app->singleton(AuthorizationServer::class, function () {
return tap($this->makeAuthorizationServer(), function ($server) {
/**
* @var $server AuthorizationServer
*/
$server->enableGrantType(
new ClientCredentialsGrant(), Passport::tokensExpireIn()
);
/** custom grant type */
$server->enableGrantType(
new PasswordOverrideGrant(
$this->app->make(UserRepository::class),
$this->app->make(RefreshTokenRepository::class)
), Passport::tokensExpireIn()
);
$server->enableGrantType(
$this->makeAuthCodeGrant(), Passport::tokensExpireIn()
);
$server->enableGrantType(
$this->makeRefreshTokenGrant(), Passport::tokensExpireIn()
);
$server->enableGrantType(
$this->makePasswordGrant(), Passport::tokensExpireIn()
);
$server->enableGrantType(
new PersonalAccessGrant(), new \DateInterval('P1Y')
);
});
});
}
PasswordOverrideGrant looks like this:
<?php
namespace App\Auth;
use App\User;
use League\OAuth2\Server\Entities\ClientEntityInterface;
use League\OAuth2\Server\Exception\OAuthServerException;
use League\OAuth2\Server\Grant\PasswordGrant;
use League\OAuth2\Server\RequestEvent;
use Psr\Http\Message\ServerRequestInterface;
class PasswordOverrideGrant extends PasswordGrant
{
protected function validateUser(ServerRequestInterface $request, ClientEntityInterface $client)
{
$username = $this->getRequestParameter('username', $request);
if (is_null($username)) {
throw OAuthServerException::invalidRequest('username');
}
$custom_hash_token = $this->getRequestParameter('hash_token', $request);
if (is_null($custom_hash_token)) {
throw OAuthServerException::invalidRequest('identifier');
}
$credentials = [
'username' => $username,
'hash_token' => $custom_hash_token,
];
$user = User::where($credentials)->first();
if ($user instanceof User === false) {
$this->getEmitter()->emit(new RequestEvent(RequestEvent::USER_AUTHENTICATION_FAILED, $request));
throw OAuthServerException::invalidCredentials();
}
return $user;
}
public function getIdentifier()
{
return 'password_override';
}
}
if you just want to use a different field than email to find a user, you can implement User::findForPassport
In my case, I have a 'login' field besides the email, so I implemented the method:
public function findForPassport($username) {
return $this->where('login', $username)->first();
}
I found out about this on passport/src/Bridge/UserRepository.php, getUserEntityByUserCredentials
Laravel 5.4
@gxrxrdx ,
Awesome!, great research, also you can add an extras condition like if the user is active ;).
@gxrxrdx , @mauriciojovel thanks for hints!
We can also throw a different OAuthServerException, so return a custom error in JSON
public function findForPassport($username)
{
$user = $this->where('email', $username)->first();
if($user !== null && $user->status == 0) {
throw new OAuthServerException('User account is not activated', 6, 'account_inactive', 401);
}
return $user;
}
this responds
{"error":"account_inactive","message":"User account is not activated"}
instead of
{"error":"invalid_credentials","message":"The user credentials were incorrect."}
I have a feeling this is illegal but you can do a check for the other columns using normal Auth Facade and then issue your token normally
` $data = ['business' => $request->input('business_code'),
'username' => $request->input('username'),
'password' => $request->input('password')];
if(!Auth::attempt($data)) {
$errors=array();
$errors["business"]=['The business code is incorrect to this user'];
return response()->json([
'message'=>'The given data was invalid',
'errors'=>$errors
],422);
}
$params=[
'grant_type'=>'password',
'client_id'=>$this->client->id,
'client_secret'=>$this->client->secret,
'username'=>request('username'),
'password'=>request('password'),
'scope'=>'*'
];
`
I want to add these conditions to login form:
$aCredential = ['email' => $Email, 'password' => $password, 'user_type' => 1];
is that possible, that I can send these param along with password grant form request? Or how can we customize the process like:
Auth::attempt($aCredential ) we do normally. (I know attempt function not available in passport)
I want to add other fields in password grant login credentials besides username and password, for example, Captcha, two-step-verification.
The password grant is only intended for legacy applications and devices that cannot support redirects. In most cases you can use the authorization code grant instead.
If you use the authorization code grant the client makes a request to the /oauth/authorize endpoint. This route is protected by the web and auth middleware. If you add anything to your login flow like a captcha, socialite login, etc and the user is not logged in when they hit the /oauth/authorize endpoint they will be prompted. If they are already logged in using the normal sessions they will just have to click 'authorize'.
As an example of what I am talking about, turn on two factor authentication for your Github account, logout of Github, then login with github to an app like https://www.netlify.com. You login to Github like you normally would, you allow Netlify to access your account, and you are redirected back to Netlify.
This is the same approach that the latest IETF draft 'OAuth 2.0 for Browser-Based Apps' recommends.
It is strongly RECOMMENDED that applications use the Authorization Code flow over the Password grant for several reasons. By redirecting to the authorization server, this provides the authorization server the opportunity to prompt the user for multi-factor authentication options, take advantage of single-sign-on sessions, or use third-party identity providers. In contrast, the Password grant does not provide any built-in mechanism for these, and must be extended with custom code.
https://tools.ietf.org/html/draft-parecki-oauth-browser-based-apps-02#section-5
FWIW there are a lot of other reasons you shouldn't use the password grant. Extending the server with custom grant types is just as bad IMO because it has all of the same issues.
Since you are using the password grant I assume you are authenticating a first party client, and showing an 'authorize' prompt would be weird. Skipping the authorization prompt is usually called "silent authentication". You can see this with Google login, where logging in to gmail won't prompt you to approve Gmail's request to access your Google account.
Passport doesn't natively support silent authentication so you would need to override the AuthorizationController. Obviously you only want to allow this for first party clients.
Most helpful comment
@gxrxrdx , @mauriciojovel thanks for hints!
We can also throw a different OAuthServerException, so return a custom error in JSON
this responds
instead of