Skip to content

Instantly share code, notes, and snippets.

@ralphschindler
Created November 26, 2014 16:23
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save ralphschindler/a585cd74973a97ffee3f to your computer and use it in GitHub Desktop.
A Barebones OAuth2 PHP Client demonstrating the "Password Grant Type"
<?php
namespace SampleOauth2Client;
class Client
{
protected $configuration = [
'token_file' => null, // path to a file to store token information
'api_authorization_token' => null, // authorization to talk to token service
'api_token_url' => null, // url to post to
'api_username' => null, // username
'api_password' => null, // password
'api_scope' => null, // scope to request access for
];
public function __construct($configuration)
{
$this->configuration = $configuration;
}
public function getAccessToken($refresh = false)
{
if (!isset($this->configuration['token_file'])) {
throw new \RuntimeException(__CLASS__ . ' requires a file to store access token information in');
}
$tokenDataFile = $this->configuration['token_file'];
$tokenData = unserialize(file_get_contents($tokenDataFile));
$tokenIsExpired = (time() > $tokenData['access']['expiration']);
if (!$tokenData || $refresh || $tokenIsExpired) {
$this->refreshTokens();
}
$tokenData = unserialize(file_get_contents($tokenDataFile));
return $tokenData['access']['token'];
}
protected function refreshTokens()
{
$context = stream_context_create([
'http' => [
'method' => 'POST',
'header' => "Authorization: Basic {$this->configuration['api_authorization_token']}\r\n"
. "Content-Type: application/x-www-form-urlencoded\r\n",
'content' => "grant_type=password&username={$this->configuration['api_username']}"
. "&password={$this->configuration['api_password']}"
. "&scope={$this->configuration['api_scope']}",
]
]);
$content = file_get_contents($this->configuration['api_token_url'], null, $context);
if (!strpos($http_response_header[0], '200 OK')) {
throw new \RuntimeException('Could not obtain an access token');
}
$decoded = json_decode($content, true);
$tokenData = [
'access' => [
'token' => $decoded['access_token'],
'expiration' => (time() + $decoded['expires_in'])
],
'refresh' => [
'token' => $decoded['refresh_token']
]
];
file_put_contents($this->configuration['token_file'], serialize($tokenData));
}
}
@philsturgeon
Copy link

Troubles here.

        'api_username' => null,             // username
        'api_password' => null,             // password

I guess you're assuming the password grant type?

return $tokenData['access']['token']

You're ignoring all the good stuff, like expiration times and refresh tokens. How do you save those?

You're assuming the whole thing is serialized file based tokens.

Lots of assumptions, which all break the uses of this package.

@philsturgeon
Copy link

I know you weren't proposing this as 100% useful always and as more of a concept, but I think making any sort of "group all the logic to make it easy" is going to depend on the use cases.

If we want to let people do all the things they want to do, we'll have as many options as we have right now.

BUT if we're talking about making it work without provider classes I can definitely get behind that as a feature suggestion as an issue on the repo. Thats not what this is though. :)

@ramsey
Copy link

ramsey commented Dec 3, 2014

@ralphschindler, with oauth2-client, there's no need to create a Client. That's part of the package. You only need to create the provider, but yes, we could make it easier with a generic provider and some configuration. If you'll open an issue on the project, I can help make it happen.

@philsturgeon
Copy link

This one probably wouldn't hurt as a bit of inspiration https://github.com/intridea/oauth2

@ralphschindler
Copy link
Author

@philsturgeon, this is bare bones, and only password grant, since I was hand-rolling, I was only going for minimal implementation, which was achieved in under 80loc. The service I wrote this for was following oauth2 password grant to the spec (I'm very well versed in the Oauth2 spec).

A couple of things missing from the oauth2-client I'd have like to seen:

  1. a configurable Provider (provide the token and/or refresh urls)
  2. built-in storage (see above, I do actually persist the tokens to disk, this might have swappable implementations)
  3. swappable http implementations (like geocoder), it's ok to default to Guzzle, although I personally like defaulting to build-in streams

I think the User entity is ok, although I might suggest that returning an array for the consuming application to marshal into an Application specific User entity might be a better strategy.

As a side-note, I find @bshaffer Oauth2 server does an excellent job of balancing configuration and implementation to produce the right level of abstraction to make it a very appealing package for all OAuth2 Server needs. I think this Oauth2 client, similarly, is very close as a client.

@philsturgeon
Copy link

Yep, so this is a difference in goals.

When you build your own OAuth 2 server, you can interact with it SO easily it hurts, just by throwing a few values at it via HTTP requests.

You are very well versed with the OAuth 2 spec, and that's all gravy, but most providers are either not versed with the spec, or flagrantly ignore it.

Some providers run their token endpoints on GET or POST for instance, which is nutty. Providers help with that.

This package was always aimed to get people working with the popular providers way before worrying about those making their own servers, because this code existed in some form (my old CodeIgniter-OAuth2 package) before either Brett or Alex had built their OAuth 2 servers, and before it seemed reasonable that people would be doing that so often as they are now.

ANYHOO, making it work without having to make your own provider certainly seems like a great goal and me and Ben are both A++ on that. Your #1 is taken care of.

2.) Probably not. We did that for OAuth 2.0 Server and it was just a mess. People want PDO wrappers and Memcache drivers and Eloquent ORM drivers and oh god just do it yourself. :)

3.) I'm gonna go with a big nope on swappable HTTP Clients, as the FIG might be able to make that much easier for us in the future. I regularly use Geocoder as a perfect example of why the FIG needs to keep fighting its fight, and HTTP Message is getting close enough to make a lot of this stuff much easier.

Guzzle is pretty much a defacto standard recently, and although some like streams Guzzle can use streams now too - not that it would make much difference to anyone WHAT its doing under the hood.

So a big yes to 1, nope on 2 and soon on 3. :)

@ralphschindler
Copy link
Author

Different goals, that's fair enough.

  1. How about refreshing access tokens automatically after they are expired when you know the service to be implementing the refresh token grant type (instead of pushing that workflow onto the consumer)?

Guzzle, meh! After months away from zf2 development, I forget about the religious sect FIG in php that believes in the one-true-interface ;)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment