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

Add web.auth.UserInfo to map the returned user from a flow #27

Merged
merged 12 commits into from
Jan 1, 2024

Conversation

thekid
Copy link
Member

@thekid thekid commented Dec 31, 2023

This pull request:

  • creates a fluent API modeled after https://github.com/xp-forge/sequence for working with the flow's result
  • adds a facility for fetching information about the authenticated user at the end of a successful OAuth flow.

Example

The following shows how code can be rewritten when using this class:

- 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'));

Mapping the user

Often, the user object returned needs to be mapped to an app-specific structure, or cached in a local database. This is where the map() function comes into play:

$auth= new SessionBased($flow, $sessions, $flow->fetchUser('https://graph.microsoft.com/v1.0/me')
  ->map(fn($graph) => ['ref' => $graph['id'], 'name' => $graph['displayName']])
  ->map(fn($user) => $users->modify(['ref' => $user['ref']], $user, upsert: true)
    ->document()
    ->properties()
  )
);

Debugging

To debug the values inside a mapping pipeline, insert a peek() call:

$auth= new SessionBased($flow, $sessions, $flow->fetchUser('https://graph.microsoft.com/v1.0/me')
  ->peek(Console::writeLine(...))
  ->map(fn($graph) => ['ref' => $graph['id'], 'name' => $graph['displayName']])
);

@thekid thekid added the enhancement New feature or request label Dec 31, 2023
@thekid
Copy link
Member Author

thekid commented Dec 31, 2023

Given the above example, there is no way to also fetch the user's avatar image as shown in this piece of code:

$auth= new SessionBased($flow, $sessions, function($client) use($storage) {
  $res= $client->fetch('https://graph.microsoft.com/v1.0/me');
  if ($res->status() >= 400) throw new AuthenticationException('Unexpected error', nameof($client));

  // Cache user photo locally
  $user= $res->value();
  $picture= $client->fetch('https://graph.microsoft.com/v1.0/me/photo/$value');
  if (200 === $picture->status()) {
    $storage->store($user['id'], 'photo', $picture->header('Content-Type'), $picture->stream());
  }

  return $user;
});

See https://learn.microsoft.com/en-us/graph/api/profilephoto-get?view=graph-rest-1.0&tabs=http#http-request

@thekid
Copy link
Member Author

thekid commented Dec 31, 2023

Given the above example, there is no way to also fetch the user's avatar image as shown in this piece of code:
[...]

After passsing the original authentication result as second argument to callbacks this can now be written as:

$auth= new SessionBased($flow, $sessions, $flow->fetchUser('https://graph.microsoft.com/v1.0/me')
  ->map(function($user, $client) use($storage) {
    $picture= $client->fetch('https://graph.microsoft.com/v1.0/me/photo/$value');
    if (200 === $picture->status()) {
      $storage->store($user['id'], 'photo', $picture->header('Content-Type'), $picture->stream());
    }
    return $user;
  })
);

@thekid
Copy link
Member Author

thekid commented Dec 31, 2023

Mapping the user info and being able to easily add debugging via peek would also be nice to have available for CAS authentication flows. This could be accomplished by adding a userInfo() method to the flow classes:

use web\auth\SessionBased:
use web\auth\cas\CasFlow;
use web\auth\oauth\OAuth2Flow;

// CAS uses the user info as returned
$flow= new CasFlow(/* ... */);
$userInfo= $flow->userInfo();

// OAuth fetches user info from a given endpoint
$flow= new OAuth2Flow(/* ... */);
$userInfo= $flow->fetchUser('https://graph.microsoft.com/v1.0/me');

// This call is the same in both cases
$auth= new SessionBased($flow, $sessions, $userInfo
  ->peek(Console::writeLine(...)) // Debugging
  ->map(fn($user) => ['ref' => $user['id'], 'name' => $user['displayName']])
);

@thekid thekid changed the title Add web.auth.oauth.UserInfo class handling OpenID user info endpoints Add web.auth.UserInfo to map the returned user from a flow Jan 1, 2024
@thekid thekid merged commit 3b456a8 into master Jan 1, 2024
18 checks passed
@thekid thekid deleted the feature/userinfo branch January 1, 2024 13:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant