datamweb / shield-oauth

OAuth for CodeIgniter Shield
https://www.shield-oauth.codeigniter4.ir/
MIT License
59 stars 16 forks source link

Microsoft OAuth Service #105

Closed nicojmb closed 6 months ago

nicojmb commented 6 months ago

Hi all,

I've added a Microsoft OAuth service and i would like to share with community.

I take this opportunity to request that it be made configurable whether or not new users can be registered

Here are de code :)

config file: App\Config\ShieldOAuthConfig.php

<?php
public array $oauthConfigs = [
    'microsoft' => [
        'client_id'     => 'Get it from Microsoft Entra ID',
        'client_secret' => 'Get it from Microsoft Entra ID',
        'allow_login' => true,
    ],
];

library file: App\Libraries\ShieldOAuth\MicrosoftOAuth.php

<?php

declare(strict_types=1);

namespace App\Libraries\ShieldOAuth;

use Datamweb\ShieldOAuth\Libraries\Basic\AbstractOAuth;
use Exception;
use stdClass;

class MicrosoftOAuth extends AbstractOAuth
{
    private static $API_CODE_URL = 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize';
    private static $API_TOKEN_URL = 'https://login.microsoftonline.com/common/oauth2/token';
    private static $API_USER_INFO_URL = 'https://graph.microsoft.com/v1.0/me';
    private static $APPLICATION_NAME = 'ShieldOAuth';

    protected string $token;
    protected $client;
    protected $config;
    protected string $client_id;
    protected string $client_secret;
    protected string $callback_url;

    public function __construct(string $token = '')
    {
        $this->token  = $token;
        $this->client = \Config\Services::curlrequest();
        $this->config = new \Config\ShieldOAuthConfig();
        $this->callback_url = base_url('oauth/' . $this->config->call_back_route);
        $this->client_id = env('ShieldOAuthConfig.microsoft.client_id', $this->config->oauthConfigs['microsoft']['client_id']);
        $this->client_secret = env('ShieldOAuthConfig.microsoft.client_secret', $this->config->oauthConfigs['microsoft']['client_secret']);
    }
    public function makeGoLink(string $state): string
    {
        try
        {
            if (empty($this->client_id))
            {
                throw new Exception('Microsoft Tenant ID is empty,');
            }

            if (empty($this->client_id))
            {
                throw new Exception('Microsoft Client ID is empty,');
            }

            if (empty($this->client_secret))
            {
                throw new Exception('Microsoft Secret is empty,');
            }

            $query = http_build_query([
                'client_id' => $this->client_id,
                'response_type' => 'code',
                'redirect_uri' => $this->callback_url,
                'response_mode' => 'query',
                'approval_prompt' => 'auto',
                'scope' => 'User.Read profile openid email',
                'state' => $state
            ]);

            $microsoftURL = self::$API_CODE_URL . '?' . $query;

            return $microsoftURL;
        }
        catch (\Throwable $e)
        {
            die($e->getMessage());
        }
    }

    public function fetchAccessTokenWithAuthCode(array $allGet): void
    {
        $client = \Config\Services::curlrequest();

        try
        {
            $response = $client->request('POST', self::$API_TOKEN_URL, [
                'form_params' => [
                    'client_id'     => $this->client_id,
                    'client_secret' => $this->client_secret,
                    'grant_type'    => 'authorization_code',
                    'redirect_uri'  => $this->callback_url,
                    'code'          => $allGet['code']
                ],
                'http_errors' => false,
            ]);

            $response = json_decode($response->getBody());

            if (!empty($response->error))
            {
                throw new Exception($response->error);
            }

            $this->setToken($response->access_token);
        }
        catch (Exception $e)
        {
            die($e->getMessage());
        }
    }

    public function fetchUserInfoWithToken(): object
    {
        try
        {
            $response = $this->client->request('GET', self::$API_USER_INFO_URL, [
                'headers' => [
                    'Authorization' => 'Bearer ' . $this->getToken(),
                ],
                'http_errors' => false,
            ]);
        }
        catch (Exception $e)
        {
            die($e->getMessage());
        }

        $response = json_decode($response->getBody());

        $userInfo = new stdClass();
        $userInfo->username = $response->userPrincipalName;
        $userInfo->email = $response->mail;
        $userInfo->first_name = $response->givenName;
        $userInfo->last_name = $response->surname;
        $userInfo->avatar = '';

        return $userInfo;
    }

    public function setColumnsName(string $nameOfProcess, object $userInfo): array
    {
        if ($nameOfProcess === 'syncingUserInfo')
        {
            $usersColumnsName = [
                $this->config->usersColumnsName['first_name'] => $userInfo->first_name,
                $this->config->usersColumnsName['last_name']  => $userInfo->last_name,
                //$this->config->usersColumnsName['avatar']  => $userInfo->last_name, // No get avantar from request
            ];
        }

        if ($nameOfProcess === 'newUser')
        {

            die('Auto-register users is disabled;');

            // Commented because i not use register new users

            // $usersColumnsName = [
            //     'username'                                    => $userInfo->username,
            //     'email'                                           => $userInfo->email,
            //     'password'                                    => random_string('crypto', 32),
            //     'active'                                          => TRUE,
            //     $this->config->usersColumnsName['first_name'] => $userInfo->first_name,
            //     $this->config->usersColumnsName['last_name']  => $userInfo->last_name,
            //     $this->config->usersColumnsName['avatar']     => $userInfo->avatar,
            // ];
        }

        return $usersColumnsName;
    }
}
datamweb commented 6 months ago

@nicojmb Thank you for sharing this driver. I know people have been looking for this. I will move this to the discussion section.