Skip to content
This repository was archived by the owner on Jan 29, 2020. It is now read-only.

An OAuth2 client is not a user #55

Closed
wants to merge 1 commit into from
Closed

An OAuth2 client is not a user #55

wants to merge 1 commit into from

Conversation

marc-mabe
Copy link
Member

Currently the OAuth2Adapter "hooks" into the zend-expressive-authentication middleware as an adapter to manage authentication with OAuth2 and allows all forms of OAuth2 authentication.

In OAuth2 there are two things that need to authenticate. First an application (OAuth2 client) and secondly a specific user but this user authentication can be omitted by some grant types like the client credentials grant as this is meant for server <-> server communication where no specific user resources are needed.

The problem is that the zend-expressive-authentication expects to authenticate a user and not something else and returning the OAuth2 client identifier as user identifier for zend-expressive-authentication can be very problematic.

Because of that the OAuth2 client should never be treated as authenticated user.

This PR only makes sure that an authenticated user will be returned by AuthenticationInterface but not an authenticated OAuth2 client.

Instead (not part of this PR as feature request) there should be another AuthenticationMiddleware specialized for authenticating OAuth2 clients - without the need for a user.

  • Are you fixing a bug?

    • Detail how the bug is invoked currently.
    • Detail the original, incorrect behavior.
    • Detail the new, expected behavior.
    • Base your feature on the master branch, and submit against that branch.
    • Add a regression test that demonstrates the bug, and proves the fix.
    • Add a CHANGELOG.md entry for the fix.
  • Are you creating a new feature?

    • Why is the new feature needed? What purpose does it serve?
    • How will users use the new feature?
    • Base your feature on the develop branch, and submit against that branch.
    • Add only one feature per pull request; split multiple features over multiple pull requests
    • Add tests for the new feature.
    • Add documentation for the new feature.
    • Add a CHANGELOG.md entry for the new feature.
  • Is this related to quality assurance?

  • Is this related to documentation?

@ezimuel
Copy link
Contributor

ezimuel commented Nov 2, 2018

@marc-mabe I see your point and I agree that zend-expressive-authentication is more oriented for user's authentication. I tried to manage also client authentication (for OAuth2) using the term identity instead of user's ID. I also added the UserInterface::getDetails() to return all the information about the identity (including also client_id for OAuth2). Actually, I also added this code to create an identity instance to manage user and client_id at the same time.

Possible solutions:
I think the Zend\Expressive\UserInterface naming can be definitely confusing and we should change it to Zend\Expressive\IdentityInterface to manage both users and clients.
Another idea will be to create another Interface, something like Zend\Expressive\ClientInterface for returning a specific client object.

@marc-mabe thoughts?

@marc-mabe
Copy link
Member Author

marc-mabe commented Nov 2, 2018

Hi @ezimuel,

Retrieving an object of UserInterface with a requirement to check that this is actually a user is indeed super confusion.

Introducing an Zend\Expressive\IdentityInterface totally makes sense for me ... but actually I would guess that most users still just assume this to be a user identity and such assumption can lead to terrible security leaks.

I like an additionalZend\Expressive\ClientInterface more ... but in this case we only have the one or the other. Also doesn't it make more sense to have in in zend-expressive-authentication-oauth2 ?

What about something like that ?:

namespace Zend\Expressive\Authentication;

interface UserInterface {
    public function getUserIdentifier(): string;
}

class DefaultUser implements UserInterface {
    public function getUserIdentifier(): string;

    /** @deprecated */
    public function getIdentifier() { return $this->getUserIdentifier(); }
}

namespace Zend\Expressive\Authentication\OAuth2;
use Zend\Expressive\Authentication\DefaultUser as BaseDefaultUser;

interface ClientInterface {
    public function getClientIdentifier(): string;
}

class DefaultUser extends BaseDefaultUser implements ClientInterface {
    public function getClientIdentifier(): string;
}

class DefaultClient implements ClientInterface {
    public function getClientIdentifier(): string;
}

@ezimuel
Copy link
Contributor

ezimuel commented Nov 2, 2018

@marc-mabe I like your approach but I would like to find an alternative solution without a BC break of Zend\Expressive\Authentication\UserInterface. A part from OAuth2, the authentication is used by others adapters: zend-expressive-authentication-basic, zend-expressive-authentication-session, and zend-expressive-authentication-zendauthentication.
Moreover, regarding the OAuth2 identity, I think we need to care also of the other information like token_id and scopes, see here.

@marc-mabe
Copy link
Member Author

The problem is that in OAuth2 there are two identities that needs to be authenticated - a client and optionally a user but zend-expressive-authentication only supports one identity - the user identity.

The only BC break I see would be the renaming of UserInterface::getIdentity to UserInterface::getUserIdentity. This can be solved at least for the DefaultUser implementation as in previous example and getIdentity could be kept as deprecated in UserInterface but you are right that all other implementations would need to get updated. And a deprecated method on an interface would lead to later BC breaks where it gets removed.

The only way to solve that would be to keep the method UserInterface::getIdentity but introduce ClientInterface::getClientIdentity.

  • zend-expressive-authentication (no change needed)
  • AuthenticationInterface only authenticates users
  • AuthenticationMiddleware populates attribute UserInterface
  • zend-expressive-authentication-oauth2
    • OAuth2Adapter implements AuthenticationInterface requires an authenticated user
    • ClientAuthenticationAdapter requires an authenticated OAuth2 client
    • ClientAuthenticationMiddleware populates attribute ClientInterface

This way you can use:

  • AuthenticationMiddleware if you require an authenticated user
  • ClientAuthenticationMiddleware if you require an authenticated oauth2 client
  • AuthenticationMiddleware + ClientAuthenticationMiddleware together if you need both

Moreover, regarding the OAuth2 identity, I think we need to care also of the other information like token_id and scopes, see here.

I think this information just needs to go into user/client details as it happens already at least for now.

@ezimuel
Copy link
Contributor

ezimuel commented Nov 13, 2018

@marc-mabe I've done some propotypes in order to support a UserInterface and a ClientInterface for zend-expressive-authentication. The PR with the propotype is zendframework/zend-expressive-authentication#39.

I introduced a new IdentityInterface and created a UserInterface that extends it. The ClientInterface should be implemented in zend-expressive-authentication-oauth2, as you suggested. This solution should prevents BC breaks for existing implementations.
Thoughts?

@ezimuel ezimuel mentioned this pull request Nov 19, 2018
@marc-mabe
Copy link
Member Author

sorry for the super late response - LGTM 👍

@marcguyer
Copy link
Contributor

marcguyer commented Jun 4, 2019

Running into this. There's a simple bug here that makes it difficult to load the client for client_credentials grant. The $userId is never null since the oauth_user_id request attribute is never null. Instead, it's an empty string. That is arguably an issue with lcobucci/jwt#249 since the sub claim in the JWT is an empty string. The workaround is to use the oauth_client_id from the $details array.

@geerteltink
Copy link
Member

webimpress requested my review here.

The past year I've digged deeper into OAuth2 and gained a lot more knowledge. The issue with OAuth2 is that it is not really designed for user authentication. There is the password grant but it is not considered good practice. A better approach would be OpenID Connect.

Having said that, if you own and or trust the server AND the application which requests a user authentication, only then the OAuth2 password grant could be used. In all other cases OpenID Connect should be used for user authentication.

I agree that an IdentityInterface makes more sense. And adding OpenID Connect for user authentication would be welcome.

weierophinney added a commit that referenced this pull request Dec 28, 2019
weierophinney added a commit that referenced this pull request Dec 28, 2019
@weierophinney
Copy link
Member

Merged to master and develop for release with 1.2.1!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants