Skip to content

Callable for customizing JWT payload#804

Merged
bshaffer merged 9 commits into
bshaffer:developfrom
afilippov1985:feature/jwt-extra-payload-callable
Nov 30, 2017
Merged

Callable for customizing JWT payload#804
bshaffer merged 9 commits into
bshaffer:developfrom
afilippov1985:feature/jwt-extra-payload-callable

Conversation

@afilippov1985
Copy link
Copy Markdown
Contributor

@afilippov1985 afilippov1985 commented Jan 27, 2017

Even easier way for customizing JWT payload

Issue #750
Issue #793
PR #795

Usage:

$myapp = .... // $myapp may be instance of your application or Service Container
$server = new OAuth2\Server($storage, array(
    'use_jwt_access_tokens' => true,
    'jwt_extra_payload' => function($client_id, $user_id, $scope) use ($myapp) {
        $data = $myapp->getSomeData();
        return array(
            'custom_param' => $data['something_important']
        );
    }
));

one more example:

$myapp = .... // $myapp may be instance of your application or Service Container
$server = new OAuth2\Server($storage, array(
    'use_jwt_access_tokens' => true,
    'jwt_extra_payload' => array($myapp, 'getSomeData')
));

$func = $this->config['jwt_extra_payload'];
$extra = $func($client_id, $user_id, $scope);
if (is_array($extra)) {
$payload = array_merge($extra, $payload);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would add $payload to the callable arguments instead of this array_merge

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean that callable must be defined like this? (pay attention that $payload passed by reference)
function(&$payload, $client_id, $user_id, $scope) {}

I think that standard JWT claims should be protected from external changes. That's why I put $payload last in array_merge()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, I man instead of:

$extra = call_user_func($this->config['jwt_extra_payload'], $client_id, $user_id, $scope);
if (is_array($extra)) {
    $payload = array_merge($extra, $payload);
}

do this:

$payload = call_user_func($this->config['jwt_extra_payload'], $payload);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see why can't change those if we need. specification does not say they are required and even if this lib does require some when we override them here it is most likely we will handle this change in other places as well.
@bshaffer what do you think?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doing so callable may break JWT. Standard JWT claims should be protected from external changes.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this feature is to generate those claims for example if you would want increase jwt token expire time for some user you could not. But if you only want to add new parameters then yes its ok

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like any of this. It violates the paradigms the library was built on. I love doing callbacks like this, but it ought to be consistent. If it's coming down to just an array merge, I think #795 is the place to do this.

'scope' => $scope
);

if (isset($this->config['jwt_extra_payload']) && is_callable($this->config['jwt_extra_payload'])) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if it is not empty and not callable maybe better throw exception because it is most likely something wrong with implementation and should be fixed instead of silently ignore this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree


if (isset($this->config['jwt_extra_payload']) && is_callable($this->config['jwt_extra_payload'])) {
$func = $this->config['jwt_extra_payload'];
$extra = $func($client_id, $user_id, $scope);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe better use call_user_func() instead of variable?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes! My mistake.

$payload = array_merge($extra, $payload);
}
} else {
throw new \InvalidArgumentException("config['jwt_extra_payload'] is not callable");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because of if (isset($this->config['jwt_extra_payload'])) will throw exception on empty values like null, '', 0 or false

@svycka
Copy link
Copy Markdown
Contributor

svycka commented Jan 27, 2017

I think this is new feature and requires tests.

if (isset($this->config['jwt_extra_payload'])) {
if (is_callable($this->config['jwt_extra_payload'])) {
$extra = call_user_func($this->config['jwt_extra_payload'], $client_id, $user_id, $scope);
if (is_array($extra)) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is required

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes it is, otherwise the array_merge below will throw an error.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bshaffer this is about array_merge error vs custom error? result will be the same :D but maybe yes more clear error message is better

);

if (isset($this->config['jwt_extra_payload'])) {
if (is_callable($this->config['jwt_extra_payload'])) {
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer these exceptions be handled like this:

if (!is_callable($this->config['jwt_extra_payload']))) {
    throw new \InvalidArgumentException("config['jwt_extra_payload'] is not callable");
}

$extra = call_user_func($this->config['jwt_extra_payload'], $client_id, $user_id, $scope);

if (!is_array($extra)) {
    throw new \InvalidArgumentException("config['jwt_extra_payload'] callable must return array");
}

$payload = array_merge($extra, $payload);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bshaffer I still don't like the idea that it is impossible to change values in $payload.

@@ -129,16 +129,17 @@ protected function createPayload($client_id, $user_id, $scope = null)
);

if (isset($this->config['jwt_extra_payload'])) {
Copy link
Copy Markdown
Contributor

@svycka svycka May 11, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since I dont like nesting I would change this to

if (!isset($this->config['jwt_extra_payload'])) {
    return $payload;
}
//...

@afilippov1985
Copy link
Copy Markdown
Contributor Author

afilippov1985 commented May 11, 2017

Let's vote:

  • Defensive programming
    $payload = array_merge($extra, $payload); // critical data can't be changed

OR

  • Flexibility
    $payload = array_merge($payload, $extra); // everything may be changed

@bshaffer
Copy link
Copy Markdown
Owner

This is looking great. We need to add the parameter here also!

@afilippov1985
Copy link
Copy Markdown
Contributor Author

How about to change parameter name to jwt_extra_payload_callable to clarify its purpose?
May be some other name?

@bshaffer
Copy link
Copy Markdown
Owner

@afilippov1985 yes, I think jwt_extra_payload_callable works for me.

Copy link
Copy Markdown
Owner

@bshaffer bshaffer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Everything looks great, two minor suggestions. Sorry this has taken so long... let's get this baby merged!!


if (isset($this->config['jwt_extra_payload_callable'])) {
if (!is_callable($this->config['jwt_extra_payload_callable'])) {
throw new \InvalidArgumentException("config['jwt_extra_payload_callable'] is not callable");
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit, but I'd rather this just be "jwt_extra_payload_callable is not callable"

throw new \InvalidArgumentException("config['jwt_extra_payload_callable'] is not callable");
}

$extra = call_user_func($this->config['jwt_extra_payload_callable'], $client_id, $user_id, $scope);
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why don't we just pass this function $payload and expect the returned array to be the new payload, e.g.:

$payload = call_user_func($this->config['jwt_extra_payload_callable'], $payload);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may break proper work of JWT
See our conversation with @svycka - https://github.com/bshaffer/oauth2-server-php/pull/804/files/86bd64e745ee34fa03bfb3456e5d2a2ab0a1c07e#r98169436
I agree to replace this
$payload = array_merge($extra, $payload);
to this
$payload = array_merge($payload, $extra);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

then, we should also rename the parameter to jwt_override_payload_callable

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm convinced. Ty for the explanation.

@bshaffer bshaffer merged commit c0c5f2c into bshaffer:develop Nov 30, 2017
bshaffer added a commit that referenced this pull request Nov 30, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants