GithubHelp home page GithubHelp logo

web-auth's Introduction

Web Authentication

Build status on GitHub XP Framework Module BSD Licence Requires PHP 7.0+ Supports PHP 8.0+ Latest Stable Version

Authentication for web services. Supports authenticating URLs with fragments such as https://example.com/#/users/thekid without losing information when redirecting.

☑ Verified with Twitter (OAuth 1), Microsoft Office 365, Facebook, GitHub and LinkedIn (OAuth 2).

Examples

HTTP basic authentication:

use web\auth\Basic;
use util\Secret;

$auth= new Basic('Administration', function($user, Secret $secret) {
  return 'admin' === $user && $secret->equals('secret') ? ['id' => 'admin'] : null;
});

return ['/' => $auth->required(function($req, $res) {
  $res->send('Hello @'.$req->value('user')['id'], 'text/plain');
})];

Authentication via Twitter:

use web\auth\SessionBased;
use web\auth\oauth\OAuth1Flow;
use web\session\ForTesting;

$flow= new OAuth1Flow(
  'https://api.twitter.com/oauth',
  [$credentials->named('twitter_oauth_key'), $credentials->named('twitter_oauth_secret')],
  $callback
);
$auth= new SessionBased($flow, new ForTesting(), function($client) {
  return $client->fetch('https://api.twitter.com/1.1/account/verify_credentials.json')->value();
});

return ['/' => $auth->required(function($req, $res) {
  $res->send('Hello @'.$req->value('user')['screen_name'], 'text/plain');
})];

The $callback parameter should be the path matching the path in the callback URI registered with Twitter.

Authentication via GitHub:

use web\auth\SessionBased;
use web\auth\oauth\OAuth2Flow;
use web\session\ForTesting;

$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(), function($client) {
  return $client->fetch('https://api.github.com/user')->value();
});

return ['/' => $auth->required(function($req, $res) {
  $res->send('Hello @'.$req->value('user')['login'], 'text/plain');
})];

The $callback parameter should be the path matching the path in the callback URI registered with GitHub.

Authentication via Office 365 Azure AD:

use util\Secret;
use web\auth\SessionBased;
use web\auth\oauth\{OAuth2Flow, BySecret, ByCertificate};
use web\session\ForTesting;

// Depending on what you have set up under "Certificates & Secrets", use one
// of the following. For certificate-based authentication, $privateKey can
// hold either the key's contents or reference it as 'file://private.key'
$credentials= new BySecret('[APP-ID]', new Secret('...'));
$credentials= new ByCertificate('[APP-ID]', '[THUMBPRINT]', $privateKey);

$flow= new OAuth2Flow(
  'https://login.microsoftonline.com/[TENANT_ID]/oauth2/v2.0/authorize',
  'https://login.microsoftonline.com/[TENANT_ID]/oauth2/v2.0/token',
  $credentials,
  $callback,
  ['openid', 'profile', 'offline_access', 'User.Read']
);
$auth= new SessionBased($flow, new ForTesting(), function($client) {
  return $client->fetch('https://graph.microsoft.com/v1.0/me')->value();
});

return ['/' => $auth->required(function($req, $res) {
  $res->send('Hello @'.$req->value('user')['login'], 'text/plain');
})];

The $callback parameter should be the path matching the path in the callback URI registered with the Azure AD application.

Authentication via CAS ("Central Authentication Service"):

use web\auth\SessionBased;
use web\auth\cas\CasFlow;
use web\session\ForTesting;

$flow= new CasFlow('https://sso.example.com/');
$auth= new SessionBased($flow, new ForTesting());

return ['/' => $auth->required(function($req, $res) {
  $res->send('Hello @'.$req->value('user')['username'], 'text/plain');
})];

Target URLs

By default, the flow instances use the request URI to determine where the service is running. Behind a proxy, this is most probably not the user-facing URI. To change this behavior, use the target() method and pass a UseURL instance as follows:

use web\auth\UseURL;
use web\auth\cas\CasFlow;

$flow= (new CasFlow('https://sso.example.com/'))->target(new UseURL('https://service.example.com/'));

web-auth's People

Contributors

thekid avatar

Watchers

 avatar  avatar

web-auth's Issues

Flow error

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

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

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

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.

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

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)

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.

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:

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.