Code Monkey home page Code Monkey logo

flow-oauth2-client's Introduction

MIT license Packagist Maintenance level: Love

OAuth 2.0 Client for Flow Framework

This Flow package provides an OAuth 2.0 client SDK. Even though it can be used as a generic OAuth2 client, it was developed as a backing library for the OpenID Connect package. That's why documentation for this package is a bit sparse at the moment and examples for generic use are missing.

At the time of writing (November 2020), this package is actively maintained and there are plans for improving functionality, test coverage and documentation.

Authorizations

This package stores states and tokens as "authorizations" in a dedicated database table.

For example, during the authorization code flow, this package needs to keep track of a "state" in order to make sense of an incoming "finish authorization" request. Another example is the client credentials flow, where an access token is stored in the authorizations table which is needed for executing authorized requests to the respective service.

Token lifetime

New Authorization tokens are created with a lifetime of 600 seconds (10 minutes) by default. The expiration date is updated during authentication if a different expires parameter is specified in the OAuth2 access token.

The default token lifetime and frequency of expired tokens to be removed automatically can be configured:

Flownative:
  OAuth2:
    Client:
      garbageCollection:
        # The probability in percent of a client shutdown triggering a garbage
        # collection which removes expired tokens.
        #
        # Examples:
        #    1    (would be a 1% chance to clean up)
        #   20    (would be a 20% chance to clean up)
        #    0.42 (would be a 0.42 % chance to clean up)
        probability: 1
    token:
      # default lifetime of new tokens in seconds
      defaultLifetime: 600

Note: By setting the defaultLifetime to null, new tokens won't expire by default.

Authorization metadata

Authorizations also may contain developer-provided metadata. For example, you may attach an account identifier to an authorization when an authorization process starts and use that information when authorization finishes to make sure that the authorization is only used for a specific account (or customer number, or participant id).

To set metadata, you need to know the authorization id when starting the authorization code flow. This code could be used in an overloaded startAuthorizationAction():

$authorizationId = $oAuthClient->generateAuthorizationIdForAuthorizationCodeGrant($this->appId);
$loginUri = $oAuthClient->startAuthorizationWithId(
    $authorizationId,
    $this->appId,
    $this->appSecret,
    $returnToUri,
    $scope
);
$oAuthClient->setAuthorizationMetadata($authorizationId, json_encode($metadata));

And later, in finishAuthorization(), you may retrieve the metadata as follows:

$authorization = $this->getAuthorization($authorizationId);
$metadata = json_decode($authorization->getMetadata());

Encryption

By default, access tokens are serialized and stored unencrypted in the "authorizations" database table. You can improve the security of your application by enabling the encrypted-at-rest feature of this package. when active, it will encrypt tokens before storing them in the database and decrypt them automatically when they are retrieved. The secret key which is needed for encryption and decryption is not stored in the database.

This package uses the "ChaCha20-Poly1305-IETF" construction for authenticated encryption / decryption of serialized tokens. It uses the "sodium" PHP extension if installed, or a polyfill implementation in pure PHP.

Generating a Secret Key

The OAuth2 Flow package provides a CLI command for generating encryption keys suitable for the currently supported encryption method:

$ ./flow oauth:generateencryptionkey
qpBzrH7icQqBKenvk8wTKROv4qcJNxslzdGo3IKXmws=

The key is base64-encoded in order to simplify handling and being able to pass the key via Flow settings.

Enabling Encryption

Set the encryption key via Flow settings (for example in your global "Configuration/Settings.yaml"). Make sure to deploy this setting securely, for example by creating the Settings file during deployment or by using environment variables.

Flownative:
  OAuth2:
    Client:
      encryption:
        base64EncodedKey: 'qpBzrH7icQqBKenvk8wTKROv4qcJNxslzdGo3IKXmws='

Verifying Encryption Configuration

When you have set the encryption key, test that everything is working as expected. Run your application so that a new authorization is created. Check the database table flownative_oauth2_client_authorization: the column serializedaccesstoken should be empty and the column encryptedserializedaccesstoken should contain a long string similar to his one:

ChaCha20-Poly1305-IETF$Mjdj4s9IFrPp6HFK$k9v3x…KQ==

There are three parts in this string, separated by two dollar signs:

  1. the construction used for encryption ("ChaCha20-Poly1305-IETF")
  2. the nonce used for this particular entry ("Mjdj4s9IFrPp6HFK")
  3. the encrypted data ("k9v3x…KQ==")

flow-oauth2-client's People

Contributors

booooza avatar bwaidelich avatar daniellienert avatar dfeyer avatar hphoeksma avatar kdambekalns avatar kitsunet avatar robertlemke avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

flow-oauth2-client's Issues

Dependency on Guzzle 6.3.*

Hey guys,

The composer.json defines the guzzle dependency as 6.3.*.

Is there a specific reason, why this package won't work with guzzle > 6.3.x?

Authorization ID must not be deterministic for authorization code flow

This plugin stores "Authorization" records in the database in order to track authorizations while they are being processed. These records contain the serialized token upon successful authorization.

In its current state, the plugin renders a deterministic identifier for these records, based on the service, client id and scope. This allows third-party code using this plugin to make sense of incoming "finish authorization" requests and relate the incoming access tokens to the original authorization request.

However, in cases where service, client id and scope are always the same for any user being authorized, there is a dangerous risk involved: when authorization of two users overlap timewise, it may happen that one user ends up using the access token of the other user, because the authorization id will be the same.

As a remedy, we must not derive the authorization id from these properties alone.

As a quick fix for the 0.x branch, ids will be random in order to keep the existing PHP method signatures. As for the current master, a change breaking backwards compatibility in its PHP API will be necessary.

No property "expires" in Authorization model

Leads to

Exception in line 498 of /var/www/app/Packages/Libraries/doctrine/orm/lib/Doctrine/ORM/Query/Parser.php: [Semantical Error] line 0, col 63 near 'expires < ?1': Error: Class Flownative\OAuth2\Client\Authorization has no field or association named expires

23 Doctrine\ORM\Query\QueryException::semanticalError("line 0, col 63 near 'expires < ?1': Error: Class Fb@&rization has no field or association named expires", Doctrine\ORM\Query\QueryException)
22 Doctrine\ORM\Query\Parser::semanticalError("line 0, col 63 near 'expires < ?1': Error: Class Fb@&rization has no field or association named expires", array|3|)
21 Doctrine\ORM\Query\Parser::processDeferredPathExpressions()
20 Doctrine\ORM\Query\Parser::getAST()
19 Doctrine\ORM\Query\Parser::parse()
18 Doctrine\ORM\Query::_parse()
17 Doctrine\ORM\Query::_doExecute()
16 Doctrine\ORM\AbstractQuery::executeIgnoreQueryCache(NULL, 1)
15 Doctrine\ORM\AbstractQuery::execute(NULL, 1)
14 Doctrine\ORM\AbstractQuery::getResult()
13 Neos\Flow\Persistence\Doctrine\Query_Original::getResult()
12 Neos\Flow\Persistence\Doctrine\QueryResult_Original::initialize()
11 Neos\Flow\Persistence\Doctrine\QueryResult_Original::rewind()
10 Flownative\OAuth2\Client\OAuthClient::removeExpiredAuthorizations()
9 Flownative\OAuth2\Client\OAuthClient::shutdownObject()
8 Neos\Flow\ObjectManagement\ObjectManager::callShutdownMethods(SplObjectStorage)
7 Neos\Flow\ObjectManagement\ObjectManager::shutdown("Runtime", "Neos\Flow\Core\Bootstrap::bootstrapShuttingDown")
6 call_user_func_array(array|2|, array|2|)
5 Neos\Flow\SignalSlot\Dispatcher::dispatch("Neos\Flow\Core\Bootstrap", "bootstrapShuttingDown", array|1|)
4 Neos\Flow\Core\Bootstrap::emitBootstrapShuttingDown("Runtime")
3 Neos\Flow\Core\Bootstrap::shutdown("Runtime")
2 Neos\Flow\Http\RequestHandler::handleRequest()
1 Neos\Flow\Core\Bootstrap::run()

Seems "expires" is dynamically set in https://github.com/flownative/flow-oauth2-client/blob/master/Classes/OAuthClient.php#L364

Lots of ERROR logs due to partial rolesFromClaims configuration

In one of our apps the system log is flooded with entries like

22-11-10 09:40:29 14743      ERROR     Flownative.OpenIdConnect.Client OpenID Connect: Ignoring role "Z01" from identity token (@!38C4.659F.8000.3A79!0001!7F12.03E3!0000!E384.4218.2B68.68D2) because there is no corresponding mapping configured.

In our case this is an expected case because we are just interested in a couple of specific roles:
We use the rolesFromClaims setting to map a role JWT claim to certain Flow roles in our application

      # ...
      providerOptions:
        roles: ['Some.App:User']
        rolesFromClaims:
          -
            name: 'roles'
            mapping:
              S01: 'Some.App:UserElevated'

=> we are only interested in the value "S01" -> leading to the elevated role. Other roles should not lead to an error.

I would suggest to reduce the severity of this message or to introduce a configuration option.

Note: The somewhat similar log "OpenID Connect: Ignoring role "%s" from identity token (%s) because there is no such role configured in Flow." correctly has the severity ERROR because it is a misconfiguration

Fix PHP 8 compatibility

PHP 8 no longer allows to call non-static class method statically (see https://php.watch/versions/8.0/non-static-static-call-fatal-error)
OAuthController::detectServiceTypes() invokes the non-static method OAuthClient::getServiceType() statically, leading to an

Fatal error: Uncaught TypeError: call_user_func_array(): Argument #1 ($function) must be a valid callback, non-static method SomeClient::getServiceType() cannot be called statically

exception

Flownative_OAuth2_Client_State grows huge

In one application of ours the Flownative_OAuth2_Client_State Cache produces 30.000 files per day (~110MB + file overhead, flooding the disk).
Running ./flow cache:collectgarbage regularily is not a viable option in our case so I would suggest to extend this package:

Use existing garbage collection to flush expired caches

We could adjust OAuthClient::shutdownObject() such that it also triggers

$this->stateCache->collectGarbage();

Remove cache entries upon authorization finalization

Alternatively (or additionally) we could extend OAuthClient::finishAuthorization() by a line

$this->stateCache->remove($stateIdentifier);

to immediately remote the cache entry upon authorization

Tokens from intercepted authentication workflows are not garbage collected

For every OAuth process an Authorization token without expiration date is generated and persisted to the database.
After successful authentication that token is "enriched" with data from the OAuth server, including an expires timestamp.

For other cases (intercepted/unsuccessful authentication) the token will stay forever, since it will never get an expiration timestamp.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.