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

Sample code loops #197

Open
kingma-sbw opened this issue Aug 29, 2023 · 4 comments
Open

Sample code loops #197

kingma-sbw opened this issue Aug 29, 2023 · 4 comments

Comments

@kingma-sbw
Copy link

kingma-sbw commented Aug 29, 2023

I've changed the sample code in this

<?php
session_start();
require_once __DIR__ . '/vendor/autoload.php';

define( 'SETTINGS', parse_ini_file( __DIR__ . '/settings.ini', true ) );

$provider = new TheNetworg\OAuth2\Client\Provider\Azure( [ 
    'clientId' => SETTINGS[ 'aad' ][ 'client-id' ],
    'clientSecret' => SETTINGS[ 'aad' ][ 'client-secret' ],
    'redirectUri' => 'https://example.com/hello.php',
    'scopes'            => ['openid'],
] );

// Set to use v2 API, skip the line or set the value to Azure::ENDPOINT_VERSION_1_0 if willing to use v1 API
$provider->defaultEndPointVersion = TheNetworg\OAuth2\Client\Provider\Azure::ENDPOINT_VERSION_2_0;

$baseGraphUri = $provider->getRootMicrosoftGraphUri(null);
$provider->scope = 'openid profile email offline_access ' . $baseGraphUri . '/User.Read';

if (isset($_GET['code']) && isset($_SESSION['OAuth2.state']) && isset($_GET['state'])) {
    if ($_GET['state'] == $_SESSION['OAuth2.state']) {
        unset($_SESSION['OAuth2.state']);

        // Try to get an access token (using the authorization code grant)
        /** @var AccessToken $token */
        $token = $provider->getAccessToken('authorization_code', [
            'scope' => $provider->scope,
            'code' => $_GET['code'],
        ]);

        // Verify token
        // Save it to local server session data
        
        $_SESSION['token'] = $token->getToken();
    } else {
        echo 'Invalid state';

        return null;
    }
} else {
    // Check local server's session data for a token
    // and verify if still valid 
    /** @var ?AccessToken $token */
    $token = $_SESSION['token']??null;
    if (isset($token)) {
       $me = $provider->get($provider->getRootMicrosoftGraphUri($token) . '/v1.0/me', $token);
       $userEmail = $me['mail'];
       if ($token->hasExpired()) {
           if (!is_null($token->getRefreshToken())) {
               $token = $provider->getAccessToken('refresh_token', [
                   'scope' => $provider->scope,
                   'refresh_token' => $token->getRefreshToken()
               ]);
           } else {
               $token = null;
           }
       }
    }
    // If the token is not found in 
    if (!isset($token)) {
        $authorizationUrl = $provider->getAuthorizationUrl(['scope' => $provider->scope]);

        $_SESSION['OAuth2.state'] = $provider->getState();

        header('Location: ' . $authorizationUrl);

        exit;
    }

	$_SESSION['token'] = $token->getToken();
}

but this loops in the https://login.microsoftonline.com/ redirect

@filip-mestric
Copy link

Hey! Have you figured a solution for this? I'm encountering the same issue. Thank you in advance!

@decomplexity
Copy link

Worth checking that the userid you are using at login is the same user principal name you used when registering the app (and hence the user name associated with the client id). Perhaps the authorization server finds a mismatch and says (in effect) 'try again' instead of bouncing the login with an error message.

@APLAY867
Copy link

Some issues with the code are that you are saving a token string and in " $token = $_SESSION['token']??null;" expecting a token object back. Saving the whole token object did not work for me. I found you need to save the serialized token in the session if you want to use the token outside of the authorization page.

////////////////////////
// following is login logic
$envPath = "" // TODO put in path to your env file
$redirect_uri = ""; // TODO put in your page url
$microsoft_connector = new MicrosoftConnector($envPath, $redirect_uri);

if (!isset($_GET['code'])) {
// If we don't have an authorization code, get one
$authUrl = $microsoft_connector->getProvider()->getAuthorizationUrl();
$_SESSION['oauth2state'] = $microsoft_connector->getProvider()->getState();
header('Location: ' . $authUrl);
exit;
} elseif (empty($_GET['state']) || ($_GET['state'] !== $_SESSION['oauth2state'])) {
// State validation
unset($_SESSION['oauth2state']);
//log 'MS Auth possible CSRF'
header("Location: " . $homeDir); // try to reset urls to remove obsolete codes.
exit();
} else {
// Now you have a token you can look up a users profile data
try {
// Try to get an access token (using the authorization code grant)
$token = $microsoft_connector->getTokenWithCode($_GET['code']); // also saves serialized token to session.
$user = $microsoft_connector->getUserDetails($token);

    $full_name = $user['displayName'];
    $email = $user['userPrincipalName'];
    
	// TODO sign in or register the user as appropriate.

} catch (Exception $e) {
    // Failed to get user details
    // log "There was an error in ms auth: " . $e->getMessage()
    exit('Unexpected error:' . $e);
}

}

//////////////////////
// to create a test calendar entry on another PHP page after user is signed in.
$microsoftConnector = new MicrosoftConnector($dbPath, $redirect_uri);
//$token = $microsoftConnector->recreateAccessTokenFromSession(); // works if done soon after login.
$token = $microsoftConnector->getTokenFromSession();
$event_array = $microsoftConnector->createTestEvent();
$eventId = $microsoftConnector->createEvent($event_array, $token);

MicrosoftConnector.txt

@kingma-sbw
Copy link
Author

this code works for me:

<?php declare(strict_types=1);
define( 'ROOT', __DIR__ . '/' );

define( 'NO_AUTH', true );
require "./inc/global.inc.php";
require "./inc/user-check.php";

define( 'MSONLINE_URL', 'https://login.microsoftonline.com/' );
define( 'MSGRAPH_SCOPE', 'https://graph.microsoft.com/User.Read' );
define( 'MSGRAPH_URL', 'https://graph.microsoft.com/v1.0/me/' );
define( 'REDIRECT_URL', 'https://' . $_SERVER['SERVER_NAME'] . '/' . basename( $_SERVER['SCRIPT_FILENAME'] ) );


$_SESSION['state'] = session_id();

if( array_key_exists( 'error', $_POST ) ) {
  LOG->critical( 'Received error ', $_POST );
  http_response_code( 500 );
  exit();
}

if( array_key_exists( 'action', $_GET ) ) {
  LOG->debug( 'Logon action: ', $_GET );

  switch($_GET['action']) {

    default:
      LOG->critical( 'Unknown action: ', [ 'action' => $_GET['action'] ] );

    case 'login':
      requestAzureAdCode();
      exit;

    case 'logout':
      LOG->notice( 'Logout' );
      session_destroy();
      $logout_url = MSONLINE_URL . SETTINGS['aad']['tennant-id'] . "/oauth2/v2.0/logout?post_logout_redirect_uri=https://sbw-media.ch";
      header( 'Location: ' . $logout_url );
      exit;
  }
}

/**
 * received an authorization code from Azure AD
 * use this to 
 * 1) redeem this code for an access token
 * 2) use the Bearer access token for Graph and get user info (most importantly Object-ID)
 * 3) store user info in session
 * 4) load the associated user from the Zeugnis DB
 *   if this user object-id is not known to us send a 403, unset session
 * 5) redirect to index.php
 */
if( array_key_exists( 'code', $_POST ) ) {
  LOG->debug( 'Got authorization code', [ 'state' => $_POST['state'], 'session_state' => $_POST['session_state'] ] );

  /* get access token */
  if( $access_token = getAccessToken( $_POST['code'] ) ) {
    if( $resource = getUserResource( $access_token ) ) {
      LOG->notice( 'AD User logged on', $resource );

      $_SESSION['AD_upn']   = $resource["userPrincipalName"];
      $_SESSION['AD_user']  = $resource["displayName"];
      $_SESSION['AD_id']    = $resource["id"];
      $_SESSION['AD_email'] = $resource["mail"];

      if( !logon() ) {
        session_unset();
        LOG->alert( 'Forbidden 403', $resource );
        header( 'HTTP/1.0 403 Forbidden' );
        exit();

      }
      LOG->info( 'Redirect to /', $resource );
      header( 'Location: /' );
      exit();

    }
    LOG->critical( 'Graph failed', [ 'access token' => $access_token ] );
    header( 'HTTP/1.0 403 Forbidden' );
  }
  LOG->critical( 'Missing code', $_POST );
  header( 'HTTP/1.0 403 Forbidden' );
}

echo '<h1>Logon to sbw.edu</h1>';
echo '<p><a href="/hello.php?action=login"</a>Login</p>';


/**
 * requestAzureAdCode
 *
 * @return void
 */
function requestAzureAdCode(): void
{
  $params = [ 

    'client_id' => SETTINGS['aad']['client-id'],
    'scope' => MSGRAPH_SCOPE,
    'redirect_uri' => REDIRECT_URL,
    'response_mode' => 'form_post',
    'response_type' => 'code',

    'state' => $_SESSION['state'],
  ];
  LOG->debug( 'Redirect to Azure AD authorizer' );
  $login_url = MSONLINE_URL . SETTINGS['aad']['tennant-id'] . "/oauth2/v2.0/authorize";
  header( 'Location: ' . $login_url . '?' . http_build_query( $params ) );
}

/**
 * getUserResource from graph
 *
 * @param  string $access_token
 * @return array
 */
function getUserResource( string $access_token ): bool|array
{
  LOG->debug( 'Getting user resource from Graph', [ 'access token' => shorten( $access_token, 15 ) ] );
  /* get user info, using the access token as */
  return sendGet( MSGRAPH_URL, [], 'Bearer ' . $access_token );
}
/**
 * getAccessToken
 * Only accept bearer type tokens
 *
 * @return string|bool
 */
function getAccessToken( string $authorization_code ): string|bool
{
  LOG->debug( 'Get access token' );

  /* Request token from Azure AD tokenizer */
  $token_url = MSONLINE_URL . SETTINGS['aad']['tennant-id'] . "/oauth2/v2.0/token";

  $params = [ 
    'client_id' => SETTINGS['aad']['client-id'],
    'client_secret' => SETTINGS['aad']['client-secret'],
    'scope' => MSGRAPH_SCOPE,
    'redirect_uri' => REDIRECT_URL,
    'response_mode' => 'form_post',
    'grant_type' => 'authorization_code',
    'response_type' => 'code id_token offline_access',

    'code' => $authorization_code,
    'state' => $_SESSION['state'],
  ];

  if( $answer = sendPost( $token_url, $params ) ) {
    if( $answer['token_type'] !== 'Bearer' ) {
      LOG->critical( "Wrong token type", $answer );
      return false;
    }
    LOG->debug( 'New state: ', array_keys( $answer ) );
    return $answer['access_token'];
  }
  return false;
}

/**
 * logonZeugnis tool
 * expects a $_SESSION['AD_id'] set to the Object ID of the user
 *
 * @return bool
 */
function logon(): bool
{
  if( !isset( $_SESSION, $_SESSION['AD_id'] ) ) {
    LOG->critical( 'No ObjectID' );
    throw new InvalidArgumentException( 'No AD ObjectID found' );
  }
  // Search for the user in our database
  return findUser( $_SESSION['AD_id'] );
}

/**
 * Send a post request, return result in array
 * @param string $url
 * @param array $payload
 *
 * @return array|bool
 */
function sendPost( string $url, array $payload ): bool|array
{
  $opts = [ 
    'http' => [ 
      'method' => 'POST',
      'header' => 'Content-Type: application/x-www-form-urlencoded',
      'content' => http_build_query( $payload )
    ]
  ];

  $context = stream_context_create( $opts );
  // we don-t want warnings, hence the @
  if( $result = @file_get_contents( $url, false, $context ) ) {

    $result = json_decode( $result, true );
    if( !isset( $result['error'] ) ) {
      return $result;
    } else {
      LOG->alert( $result['error_description'] );
      exit;
    }
  }
  LOG->alert( "sendPost: could not read response." );
  exit;
}

/**
 * Send a get request
 *
 * @param  mixed $url 
 * @param  mixed $payload
 * @param  mixed $authorization
 * @return array
 */
function sendGet( string $url, array $payload, string $authorization ): array|bool
{
  $opts = [ 
    'http' => [ 
      'method' => 'GET',
      'header' => [ 
        'Content-Type: application/x-www-form-urlencoded',
        'Authorization: ' . $authorization
      ]
    ]
  ];

  $context = stream_context_create( $opts );
  // we don-t want warnings, hence the @
  $result = @file_get_contents( $url . '?' . http_build_query( $payload ), false, $context );

  if( $result = json_decode( $result, true ) ) {
    return $result;
  } else {
    LOG->alert( $result );
    return false;
  }
}

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

4 participants