Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OAuth setup simplification #26

Open
thekid opened this issue Dec 30, 2023 · 5 comments
Open

OAuth setup simplification #26

thekid opened this issue Dec 30, 2023 · 5 comments

Comments

@thekid
Copy link
Member

thekid commented Dec 30, 2023

Currently, this is the typical code setup inside a web applications' routes() method:

use web\auth\SessionBased;
use web\auth\oauth\{OAuth2Flow, BySecret};

$flow= new OAuth2Flow(
  'http://localhost:8443/oauth/common/authorize',
  'http://localhost:8443/oauth/common/token',
  new BySecret('613aacd1f95ce7ee1b04', '...'),
  '/',
  ['user'],
);
$auth= new SessionBased($flow, $sessions, function($client) use($users) {
  $me= $client->fetch('http://localhost:8443/graph/me')->value();
  return $users->upsert([
    'handle' => $me['id'],
    'name'   => $me['name'],
    // continues mapping fields, shortened for brevity
  ]);
});

return ['/' => $auth->required($frontend)];

If I want to make this configurable and integrate well with several services, there's a lot to do:

  • 2 separate URLs for authorize and token endpoints
  • Another one to choose between the credentials using BySecret (client ID and secret) or ByCertificate (client ID, thumbprint and certificate)
  • Depending on the above, 2 or 3 more configuration values for the credentials
  • An additional one for the scopes
  • Another one for the userinfo endpoint
  • ...and finally something to map the fields returned by this userinfo endpoint

👉 This boils down to almost 10 configuration options that have to be set in order to get an OAuth workflow running!

@thekid
Copy link
Member Author

thekid commented Dec 30, 2023

OIDC aims at simplifying this by adding all these (and some more options) to a configuration file to a well known location, see https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig

Examples

Implementation

Given that, we could provide an API such as the following:

use web\auth\SessionBased;
use web\auth\oauth\Provider;

$oidc= new Provider(
  'https://login.windows.net/common/',
  new BySecret('613aacd1f95ce7ee1b04', '...'),
);
$auth= new SessionBased($oidc->flow(), $sessions, function($user) {
  // Potentially include user field mapping logic...
  return $users->upsert($user);
});

...which would the mean only having to configure 1 URL instead of 3.

Caveat

However, not all sites support this configuration, e.g. GitHub, see https://stackoverflow.com/questions/52157568/what-is-github-well-known-openid-configuration-url

@thekid
Copy link
Member Author

thekid commented Dec 30, 2023

Another potential for compressing this is introducing a specialized OAuth URI:

Syntax

"<scheme>://<client-id>:<secret>@<host>[:<port>]/<common-path>/{authorize,token}?<params>"

Examples

  • GitHub: https://613aacd1f95ce7ee1b0f:[email protected]/login/oauth/{authorize,access_token}?scope=user
  • O365: https://b105d05c-6952-444b-84c0-53487c2ca24f:[email protected]/ee920d54-46e4-4e1e-aa99-6d010a689325/oauth2/v2.0/{authorize,token}?scope=openid,User.Read
  • Facebook: https://70051176497917f:[email protected]/v18.0/oauth/{authorize,access_token}?scope=public_profile,email

Caveats

This breaks for Facebook when using https://www.facebook.com/v18.0/dialog/oauth as authorization endpoint and https://graph.facebook.com/v18.0/oauth/access_token as token endpoint as documented here. As seen above, this does not need to be a problem, but requires extra cognitive load for developers!

@thekid
Copy link
Member Author

thekid commented Dec 30, 2023

Other libraries such as The PHPLeague OAuth client go down the road of encapsulating these well-known URLs inside implementing classes, see e.g. https://github.com/thephpleague/oauth2-facebook/blob/main/src/Provider/Facebook.php. This leads to hundreds of small libraries like the ones listed on their third-party page, with the problem of them becoming outdated quickly.

@thekid
Copy link
Member Author

thekid commented Dec 31, 2023

Fetching the user info is a small improvement to the API, see #27:

- use web\auth\{AuthenticationError, SessionBased};
+ use web\auth\SessionBased;
  use web\auth\oauth\OAuth2Flow;

  $flow= new OAuth2Flow(/* shortened for brevity */);
- $auth= new SessionBased($flow, $sessions, function($client) {
-   $res= $client->fetch('https://graph.microsoft.com/v1.0/me');
-   if ($res->status() >= 400) throw new AuthenticationError('Unexpected status '.$res->status());
-   return $res->value();
- });
+ $auth= new SessionBased($flow, $sessions, $flow->fetchUser('https://graph.microsoft.com/v1.0/me'));

@thekid
Copy link
Member Author

thekid commented Jan 1, 2024

For an example, see thekid/crews#3

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

No branches or pull requests

1 participant