Skip to content

Using Resource Owner Password Credentials flow

Nikita Bulai edited this page Nov 2, 2022 · 23 revisions

NOTE: See this issue in case this implementation is buggy: https://github.com/doorkeeper-gem/doorkeeper/issues/475#issuecomment-107501342.

In this flow, a token is requested in exchange for the resource owner credentials (username and password):

Configuration

To use this flow you first have to tell doorkeeper how to authenticate the resource owner with username/password:

Doorkeeper.configure do
  resource_owner_from_credentials do |routes|
    User.authenticate!(params[:username], params[:password])
  end
end

For Doorkeeper 2.1+, you'll need to add password as an acceptable grant type. In your initializer, make sure password is part of your grant_flows so you have at least (you can have others):

grant_flows %w[password]

Newer versions of devise don't provide the authenticate! method anymore, and you can use:

Doorkeeper.configure do
  resource_owner_from_credentials do |routes|
    user = User.find_for_database_authentication(email: params[:username])
    if user&.valid_for_authentication? { user.valid_password?(params[:password]) } && user&.active_for_authentication?
      request.env['warden'].set_user(user, scope: :user, store: false)
      user
    end
  end
end

Alternatively, you can also run the authentication through warden. This requires some trickery though:

Doorkeeper.configure do
  resource_owner_from_credentials do |routes|
    request.params[:user] = {email: request.params[:username], password: request.params[:password]}
    request.env["warden"].logout(:user)
    request.env["devise.allow_params_authentication"] = true
    # Set `store: false` to stop Warden from storing user in session
    # https://github.com/doorkeeper-gem/doorkeeper/issues/475#issuecomment-305517549
    request.env["warden"].authenticate!(scope: :user, store: false)
  end
end

Testing

For testing you can use the oauth2 ruby gem:

client = OAuth2::Client.new('the_client_id', 'the_client_secret', site: "http://example.com")
access_token = client.password.get_token('user@example.com', 'sekret')
puts access_token.token

That will make a POST request to the OAuth providers /oauth/token endpoint, with the params:

{
  "client_id": "the_client_id",
  "client_secret": "the_client_secret",
  "grant_type": "password",
  "username": "user@example.com",
  "password": "sekret"
}

Then, you'll receive the access token back in the response:

{
  "access_token": "1f0af717251950dbd4d73154fdf0a474a5c5119adad999683f5b450c460726aa",
  "token_type": "bearer",
  "expires_in": 7200
}

Links:

Clone this wiki locally