PHP Authentication

Brian Retterer

@bretterer

 

brian@brianretterer.com

Lessons Learned

Who is Brian Retterer?

Developer Advocate

at Okta

Self Proclaimed Whovian

Laravel / WordPress / PHP Hacker

http://charisteapot.tumblr.com/post/116544339467/tardis-charisteapot-doctor-who-fanart-this-is

DR 11 Forever!

The
Early
Days

The If Checks!!!

<?php
$errors = array();

if ( !isset($_REQUEST['email']) ) {
  $errors[] = 'Email is not set';

  if ( !isset($_REQIEST['password']) ) {
    $errors[] = 'Password is not set';

    if ( $_REQUEST['email'] == '' ) {
      $errors[] = 'Email can not be empty';

      if ( $_REQUEST['password'] == '') {
        $errors[] = 'Password can not be empty';

        if ( !empty($user = get_user_by_email( $_REQUEST['email']) ) ) {
          $errors[] = 'User Not Found';

          if ( $user['password'] !== md5($user['salt'] . $_REQUEST['salt']) ) {
            $errors[] = 'Password Is Incorrect';
          }
        }
      }
    }
  }
}

Security by Obscurity

CREATE TABLE Users (
  id INT NOT NULL AUTO_INCREMENT,
  email VARCHAR(80) NOT NULL,
  display_name VARCHAR(50) NOT NULL,
  favorite_phrase CHAR(41) NOT NULL, //NOT PASSWORD
  PRIMARY KEY (user_id),
  UNIQUE INDEX (email)
);

The Hashing

<?php

md5('myPassword');
<?php

$salt = 'GlobalSaltSecretPhrase';

md5( md5($salt) . md5('MyPassword') );
<?php

$salt = 'GlobalSaltSecretPhrase';

md5( md5($salt) . md5('MyPassword') . md5($userCreatedAt) );

Hall
Of
Shame

Don't Use Admin

Too Much Info

Too Much Info

WHAT?!?!?!

Errrr.. Gross

Login &
Registration
Guidelines

Keep it simple

Keep it simple

Don't give away too much

Username vs Email

Remember
Me
Option

Amazon

Amazon

Amazon

Twitter

Twitter

Twitter

Password

Hashing

MD5

MD5

  • Designed in 1991
  • 128-bit hash
  • RFC 1321
  • Cracked in 2012 
  • Mostly used now for checksum
  • and bad password storage

MCRYPT

MCRYPT

  • Introduced in PHP 4
  • Complex to implement
  • Requires extension to pull random bytes
    • mcrypt_create_iv
    • PHP 7  has random_bytes
  • openssl_encrypt instead

BCRYPT

BCRYPT

  • 1999
  • Incorporates a salt
  • Adaptive 
  • Default hash algorithm for OpenBSD Passwords
  • Defines itself

BCRYPT

$2y$10$HFVEs5GKR/mGj1pj2MJWg.HQ5TQjCQ1.IRGtXvOP7ofZTA1EAI9NG

$2y$ - Corrected Version of Bcrypt

      $2a$ - Potentially Buggy

         $2x$ - Known buggy hash

BCRYPT

$2y$10$HFVEs5GKR/mGj1pj2MJWg.HQ5TQjCQ1.IRGtXvOP7ofZTA1EAI9NG

10$ - Cost

$timeTarget = 0.05; // 50 milliseconds 

$cost = 8;
do {
    $cost++;
    $start = microtime(true);
    password_hash("test", PASSWORD_BCRYPT, ["cost" => $cost]);
    $end = microtime(true);
} while (($end - $start) < $timeTarget);

echo "Appropriate Cost Found: " . $cost . "\n";

BCRYPT

$2y$10$HFVEs5GKR/mGj1pj2MJWg.HQ5TQjCQ1.IRGtXvOP7ofZTA1EAI9NG

HFVEs5GKR/mGj1pj2MJWg. - Salt

BCRYPT

$2y$10$HFVEs5GKR/mGj1pj2MJWg.HQ5TQjCQ1.IRGtXvOP7ofZTA1EAI9NG

HQ5TQjCQ1.IRGtXvOP7ofZTA1EAI9NG - Hashed Text

PASSWORD_HASH

PASSWORD_HASH

password_hash('password', PASSWORD_DEFAULT, ['cost' => 10]);
// $2y$10$HFVEs5GKR/mGj1pj2MJWg.HQ5TQjCQ1.IRGtXvOP7ofZTA1EAI9NG

password_hash('password', PASSWORD_BCRYPT, ['cost' => 10]);
// $2y$10$OYicKVXdu0a5RTus6HuJfuDCun8caWE/PD18SwdGKJQVbWH0s.zd6


// PRE PHP 7.0
password_hash('password', PASSWORD_BCRYPT, ['salt' => 'somerandom22charstrng.']);
//$2y$10$somerandom22charstrng.pO7bKuthd0JmLDOsNPqVL13XmCqEs9e

Using PASSWORD_BCRYPT limits password to 72 chars

Always 60 characters total

PASSWORD_VERIFY

PASSWORD_VERIFY

password_verify('password', '$2y$10$somerandom22charstrng.pO7bKuthd0JmLDOsNPqVL13XmCqEs9e');
// true

All Items needed to hash password is provided.

$hashed = '$2y$10$somerandom22charstrng.pO7bKuthd0JmLDOsNPqVL13XmCqEs9e';

$salt = substr($hashed, 7, 22);

$newHashed = password_hash('password', PASSWORD_BCRYPT, ['salt' => $salt]);

$hashed == $newHashed
// true

PASSWORD_NEEDS_REHASH

password_needs_rehash("$2y$10$somerandom22charstrng.pO7bKuthd0JmLDOsNPqVL13XmCqEs9e", PASSWORD_DEFAULT));
// false


password_needs_rehash("$2x$10$somerandom22charstrng.pO7bKuthd0JmLDOsNPqVL13XmCqEs9e", PASSWORD_DEFAULT));
// true
if(password_needs_rehash(...)) {
    $newHash = password_hash(...);
    storePassword($newHash);
}

Social

Integration

Don't overdo it

Don't reinvent the wheel

http://github.com/laravel/socialite

OAuth
2.0

Info on Oauth 2.0

  • Released 2012
  • RFC 6749
  • Widely used
  • 4 main grant types

Implicit Grant

The implicit grant type is used to obtain access tokens (it does not support the issuance of refresh tokens) and is optimized for public clients known to operate a particular redirection URI.  These clients   are typically implemented in a browser using a scripting language such as JavaScript.

Client Credentials Grant

The client can request an access token using only its client credentials (or other supported means of authentication) when the  client is requesting access to the protected resources under its control, or those of another resource owner that have been previously arranged with the authorization server

POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache

{
  "access_token":"2YotnFZFEjr1zCsicMWpAA",
  "token_type":"example",
  "expires_in":3600,
  "example_parameter":"example_value"
}

Authorization Code Grant

The authorization code grant type is used to obtain both access tokens and refresh tokens and is optimized for confidential clients. Since this is a redirection-based flow

Resource Owner Password Grant

The resource owner password credentials grant type is suitable in cases where the resource owner has a trust relationship with the client, such as the device operating system or a highly privileged application.

PHP League

http://oauth2-client.thephpleague.com/

$provider = new \League\OAuth2\Client\Provider\GenericProvider([
    'clientId'                => 'demoapp',    // The client ID assigned to you by the provider
    'clientSecret'            => 'demopass',   // The client password assigned to you by the provider
    'redirectUri'             => 'http://example.com/your-redirect-url/',
    'urlAuthorize'            => 'http://brentertainment.com/oauth2/lockdin/authorize',
    'urlAccessToken'          => 'http://brentertainment.com/oauth2/lockdin/token',
    'urlResourceOwnerDetails' => 'http://brentertainment.com/oauth2/lockdin/resource'
]);

//Get Authorization Url
$authorizationUrl = $provider->getAuthorizationUrl();

//Verify Code and get access token
$accessToken = $provider->getAccessToken('authorization_code', [
    'code' => $_GET['code']
]);

Multi
Factor

Auth

MFA Types

SMS

TOTP

Others

JSON
Web
Tokens

About JWT's

  • RFC 7519
  • URL Safe
  • Signable

JWT

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImp0aSI6ImM2NTcxZjg2LWNjODktNGY2Ny1iOTEzLTVjN2ZlMTcxM2Y1OSIsImlhdCI6MTQ3OTM5MDQ5OSwiZXhwIjoxNDc5Mzk0MDk5fQ.Yuy7VeMNKqKwmSCTXHQ_DdOrg2v42afKgZzmg8ISIIE

Header

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

{
 "typ": "JWT",
 "alg": "HS256"
}

JWT

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImp0aSI6ImM2NTcxZjg2LWNjODktNGY2Ny1iOTEzLTVjN2ZlMTcxM2Y1OSIsImlhdCI6MTQ3OTM5MDQ5OSwiZXhwIjoxNDc5Mzk0MDk5fQ.Yuy7VeMNKqKwmSCTXHQ_DdOrg2v42afKgZzmg8ISIIE

Signature

Yuy7VeMNKqKwmSCTXHQ_DdOrg2v42afKgZzmg8ISIIE

JWT

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImp0aSI6ImM2NTcxZjg2LWNjODktNGY2Ny1iOTEzLTVjN2ZlMTcxM2Y1OSIsImlhdCI6MTQ3OTM5MDQ5OSwiZXhwIjoxNDc5Mzk0MDk5fQ.Yuy7VeMNKqKwmSCTXHQ_DdOrg2v42afKgZzmg8ISIIE

Claims

eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImp0aSI6ImM2NTcxZjg2LWNjODktNGY2Ny1iOTEzLTVjN2ZlMTcxM2Y1OSIsImlhdCI6MTQ3OTM5MDQ5OSwiZXhwIjoxNDc5Mzk0MDk5fQ

{
 "sub": "1234567890",
 "name": "John Doe",
 "admin": true,
 "jti": "c6571f86-cc89-4f67-b913-5c7fe1713f59",
 "iat": 1479390499,
 "exp": 1479394099
}

Claims

"iss" (Issuer) Who issues this claim?
"sub" (Subject) Who is the subject?
"aud" (Audience) Who is the target of JWT?
"exp" (Expiration Time) Seconds since epoch
"nbf" (Not Before) seconds since epoch
"iat" (Issued At) Seconds since epoch
"jti" (JWT ID) Unique id for JWT

How to generate?

composer require firebase/php-jwt

JWT::encode([
    'sub' => '1234567890',
    'jti' => Ramsey\Uuid\Uuid::uuid4(),
    'iat' => 1479390499,
    'exp' => 1479394099,
    'nbf' => 1479390499,
    'admin' => true,
    'favorite_colors' => ['orange', 'blue']
], 'secret', 'HS256');

How to decode?

composer require firebase/php-jwt

try{
  JWT::decode(
    'eyJ0e...zI1NiJ9.eyJzdWIi...k0MDk5fQ.Yuy7...8ISIIE', 
    'secret', 
    ['HS256']
  );
} catch( SignatureInvalidException $e ) {
    // Signature did not work, can use but with caution
} catch( BeforeValidException $e ) {
    // This token is not yet valid
} catch( ExpiredException $e ) {
    // This token has expired
} 

JSON Web Tokens

https://jsonwebtoken.io/

Cookies
Vs
Session

Storing in Local Storage

<script>
    window.sessionStorage.accessToken = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImp0aSI6ImM2NTcxZjg2LWNjODktNGY2Ny1iOTEzLTVjN2ZlMTcxM2Y1OSIsImlhdCI6MTQ3OTM5MDQ5OSwiZXhwIjoxNDc5Mzk0MDk5fQ.Yuy7VeMNKqKwmSCTXHQ_DdOrg2v42afKgZzmg8ISIIE';
</script>

Flaws of Local Storage

  • Accessible via script
  • Insecure packages
  • XSS Attacks

PHP Session

  • Not secure!
  • Session Id Hacking
  • Not scalable
  • Who wants to use super global? ($_SESSION)
  • Don't forget session_start()

Cookie Storage

composer require symfony/http-foundation

$cookie = new Cookie(
  'accessToken', // name
  'eyJ...J9.ey...fQ.Yu...IE', // payload
  1479397344, //expire time
  '/', // path
  'example.io', // domain
  true, // secure only
  true, // http only
  false // send without url encoding
);

$response->headers->setCookie($cookie);

Cookies

  • Immune to XSS
  • Can be served over HTTPS only
  • No State required
  • Vulnerable to CSRF
  • Access from JS can be blocked

PHP Authentication

Brian Retterer

@bretterer

 

brian@brianretterer.com

Lessons Learned

Thank You!

PHP Authentication - Lessons Learned - KCDC

By Brian Retterer

PHP Authentication - Lessons Learned - KCDC

KCDC 2017

  • 1,323