GithubHelp home page GithubHelp logo

web-auth's Issues

Undefined array key "token_type"

Using LinkedIn OAuth this error appears after authenticating in line 93 of OAuth2Flow.class.php

91      return new ByAccessToken(
92        $stored['access_token'],
93        $stored['token_type'],
94        $stored['scope'] ?? null,
95        $stored['expires_in'] ?? null,
96        $stored['refresh_token'] ?? null
97      );

This is because the hash returned by LinkedIn only contains the keys access_token and expires_in.

Flow error

Flow error, session state 4c931222a3682e22ae3cc5484ee6d89f != server state a29fc8d9132ca9fa0610cb9ff16b5dd2

Unclear what's causing this. Can only be fixed by deleting cookies

PHP 8.1 compatibility

  • sscanf(): Passing null to parameter 1 ($string) of type string is deprecated" in ::sscanf() (Basic.class.php, line 51, occured once)
  • rtrim(): Passing null to parameter 1 ($string) of type string is deprecated" in ::rtrim() (UseURL.class.php, line 24, occured once)
  • explode(): Passing null to parameter 2 ($string) of type string is deprecated" in ::explode() (Parameters.class.php, line 85, occured once)
  • stristr(): Passing null to parameter 1 ($haystack) of type string is deprecated" in ::stristr() (HttpResponse.class.php, line 40, occured once)
  • explode(): Passing null to parameter 2 ($string) of type string is deprecated" in ::explode() (Parameters.class.php, line 85, occured once)
  • sodium_crypto_secretbox(): Passing null to parameter 1 ($message) of type string is deprecated" in ::sodium_crypto_secretbox() (Secret.class.php, line 66, occured once)
  • strlen(): Passing null to parameter 1 ($string) of type string is deprecated" in ::strlen() (Secret.class.php, line 137, occured once)

OAuth setup simplification

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!

Access to authentication

Currently web handlers have access to the authenticated user via the request value named user, which is returned by the authentication flow. However, the following use-cases are not possible without workarounds:

  • Logging out the user
  • Updating the user
  • Aggregating other information alongside the user

To make this possible, a new value authentication could be passed to the request containing methods to access user and session.

JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and Authorization Grants

From a Microsoft Graph specific implementation:

use peer\URL;
use util\{UUID, Secret};
use lang\IllegalStateException;

abstract class Credentials {
  protected $tenant, $client;

  public function __construct($tenant, $client) {
    $this->tenant= $tenant;
    $this->client= $client;
  }

  /** @return string */
  public function tenant() { return $this->tenant; }

  /** @return string */
  public function client() { return $this->client; }

  /**
   * Returns request parameters for a given endpoint
   *
   * @param  peer.URL $endpoint
   * @return [:string]
   */
  public abstract function request(URL $endpoint);
}

class ClientSecret extends Credentials {
  private $secret;

  public function __construct($tenant, $client, $secret) {
    parent::__construct($tenant, $client);
    $this->secret= $secret instanceof Secret ? $secret : new Secret($secret);
  }

  public function request(URL $endpoint) {
    return ['client_id' => $this->client, 'client_secret' => $this->secret->reveal()];
  }
}

class WithCertificate extends Credentials {
  private $certificate, $key;

  public function __construct($tenant, $client, $certificate, $key) {
    parent::__construct($tenant, $client);
    $this->certificate= $certificate;
    $this->key= $key;
  }

  public function request(URL $endpoint) {
    $time= time();
    $jwt= new JWT(['alg' => 'RS256', 'typ' => 'JWT', 'x5t' => JWT::base64(hex2bin($this->certificate))], [
      'aud' => $endpoint->toString(),
      'exp' => $time + 3600,
      'iss' => $this->client,
      'jti' => UUID::timeUUID()->hashCode(),
      'nbf' => $time,
      'sub' => $this->client,
    ]);

    return [
      'client_id'             => $this->client,
      'client_assertion'      => $jwt->sign($this->key),
      'client_assertion_type' => 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
    ];
  }
}

/**
 * Very simple JWT implementation (only supporting `RS256`)
 *
 * @see  https://tools.ietf.org/html/rfc7519
 * @ext  openssl
 */
class JWT {
  private $header, $payload;

  /** Creates a new JWT with a given header and payload */
  public function __construct(array $header, array $payload) {
    $this->header= $header;
    $this->payload= $payload;
  }

  /** URL-safe Base64 encoding */
  public static function base64(string $bytes): string {
    return strtr(rtrim(base64_encode($bytes), '='), '+/', '-_');
  }

  /** Sign JWT and return token */
  public function sign(string $key): string {
    $input= self::base64(json_encode($this->header)).'.'.self::base64(json_encode($this->payload));

    // Hardcode SHA256 signing via OpenSSL here, would need algorithm-based
    // handling in order for this to be a full implementation, see e.g.
    // https://github.com/firebase/php-jwt/blob/v6.2.0/src/JWT.php#L220
    if (!openssl_sign($input, $signature, openssl_pkey_get_private($key), 'SHA256')) {
      throw new IllegalStateException(openssl_error_string());
    }

    return $input.'.'.self::base64($signature);
  }
}

See:

Use authorization information from OAuth

By omitting the lookup function, we essentially return the BySignedRequests or ByAccessToken instances. These should be serialized properly to be able to reuse them with e.g. the classes from xp-forge/rest-client.

use web\auth\SessionBased;
use web\auth\oauth\OAuth2Flow;
use web\session\ForTesting;
use webservices\rest\Endpoint;

$flow= new OAuth2Flow(
  'https://github.com/login/oauth/authorize',
  'https://github.com/login/oauth/access_token',
  [$credentials->named('github_oauth_key'), $credentials->named('github_oauth_secret')],
  $callback
);
$auth= new SessionBased($flow, new ForTesting());

return ['/' => $auth->required(function($req, $res) {
  $endpoint= (new Endpoint('https://api.github.com/'))->with('Authorization', $req->value('user'));

  $user= $endpoint->resource('user')->get()->value();
  $res->send('Hello @'.$user['login'], 'text/html');
})];

Handle /?error=...

This occurs when authentication fails - some providers decide not to shown an error page on their own but to redirect back to the original page and pass on an error response:

If the request fails due to a missing, invalid, or mismatching redirection URI, or if the client identifier is missing or invalid, the authorization server SHOULD inform the resource owner of the error and MUST NOT automatically redirect the user-agent to the invalid redirection URI.

See https://tools.ietf.org/html/rfc6749#section-4.1.2.1

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.