From 7c0b7522423943131f680e74123b71ccd3989541 Mon Sep 17 00:00:00 2001 From: Katzen48 Date: Tue, 15 Mar 2022 00:25:14 +0100 Subject: [PATCH] added Minecraft Provider (#793) Co-authored-by: Lucas Michot Co-authored-by: atymic --- OktaExtendSocialite.php | 18 ++++ Provider.php | 179 ++++++++++++++++++++++++++++++++++++++++ README.md | 108 ++++++++++++++++++++++++ composer.json | 33 ++++++++ 4 files changed, 338 insertions(+) create mode 100644 OktaExtendSocialite.php create mode 100644 Provider.php create mode 100644 README.md create mode 100644 composer.json diff --git a/OktaExtendSocialite.php b/OktaExtendSocialite.php new file mode 100644 index 0000000..96b9621 --- /dev/null +++ b/OktaExtendSocialite.php @@ -0,0 +1,18 @@ +extendSocialite('okta', Provider::class); + } +} diff --git a/Provider.php b/Provider.php new file mode 100644 index 0000000..18c0db8 --- /dev/null +++ b/Provider.php @@ -0,0 +1,179 @@ +getConfig('base_url'); + } + + /** + * Returns the Auth Server ID based on config option 'auth_server_id'. + * + * @return string + */ + protected function getAuthServerId() + { + $authServerId = (string) $this->getConfig('auth_server_id'); + + return $authServerId === '' ? $authServerId : $authServerId.'/'; + } + + /** + * Get the Okta sever URL. + * + * @return string + */ + protected function getOktaServerUrl(): string + { + return $this->getOktaUrl().'/oauth2/'.$this->getAuthServerId(); + } + + /** + * {@inheritdoc} + */ + public static function additionalConfigKeys() + { + return ['base_url', 'auth_server_id']; + } + + /** + * {@inheritdoc} + */ + protected function getAuthUrl($state) + { + return $this->buildAuthUrlFromBase($this->getOktaServerUrl().'v1/authorize', $state); + } + + /** + * {@inheritdoc} + */ + protected function getTokenUrl() + { + return $this->getOktaServerUrl().'v1/token'; + } + + /** + * {@inheritdoc} + */ + protected function getUserByToken($token) + { + $response = $this->getHttpClient()->get($this->getOktaServerUrl().'v1/userinfo', [ + RequestOptions::HEADERS => [ + 'Authorization' => 'Bearer '.$token, + ], + ]); + + return json_decode((string) $response->getBody(), true); + } + + /** + * Get the client access token response. + * + * @param array|string $scopes + * + * @return array + */ + public function getClientAccessTokenResponse($scopes = null) + { + $scopes = $scopes ?? $this->getScopes(); + $response = $this->getHttpClient()->post($this->getTokenUrl(), [ + RequestOptions::AUTH => [$this->clientId, $this->clientSecret], + RequestOptions::HEADERS => ['Cache-Control' => 'no-cache'], + RequestOptions::FORM_PARAMS => [ + 'grant_type' => 'client_credentials', + 'scope' => $this->formatScopes((array) $scopes, $this->scopeSeparator), + ], + ]); + + return json_decode((string) $response->getBody(), true); + } + + /** + * {@inheritdoc} + */ + protected function mapUserToObject(array $user) + { + return (new User())->setRaw($user)->map([ + 'id' => Arr::get($user, 'sub'), + 'email' => Arr::get($user, 'email'), + 'email_verified' => Arr::get($user, 'email_verified', false), + 'nickname' => Arr::get($user, 'nickname'), + 'name' => Arr::get($user, 'name'), + 'first_name' => Arr::get($user, 'given_name'), + 'last_name' => Arr::get($user, 'family_name'), + 'profileUrl' => Arr::get($user, 'profile'), + 'address' => Arr::get($user, 'address'), + 'phone' => Arr::get($user, 'phone'), + 'id_token' => $this->credentialsResponseBody['id_token'] ?? null, + ]); + } + + /** + * {@inheritdoc} + */ + protected function getTokenFields($code) + { + return array_merge(parent::getTokenFields($code), [ + 'grant_type' => 'authorization_code', + ]); + } + + /** + * @param string $idToken + * @param string|null $redirectUri + * @param string|null $state + * + * @return string + */ + public function getLogoutUrl(string $idToken, string $redirectUri = null, string $state = null) + { + $url = $this->getOktaServerUrl().'v1/logout'; + + $params = http_build_query(array_filter([ + 'id_token_hint' => $idToken, + 'post_logout_redirect_uri' => $redirectUri, + 'state' => $state, + ])); + + return "$url?$params"; + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..69e738e --- /dev/null +++ b/README.md @@ -0,0 +1,108 @@ +# Okta + +```bash +composer require socialiteproviders/okta +``` + +## Installation & Basic Usage + +Please see the [Base Installation Guide](https://socialiteproviders.com/usage/), then follow the provider specific instructions below. + +### Add configuration to `config/services.php` + +```php +'okta' => [ + 'base_url' => env('OKTA_BASE_URL'), + 'client_id' => env('OKTA_CLIENT_ID'), + 'client_secret' => env('OKTA_CLIENT_SECRET'), + 'redirect' => env('OKTA_REDIRECT_URI') +], +``` + +#### Custom Auth Server + +If you're using Okta Developer you should set `auth_server_id` config option appropriately. It should be set to "default", or to the server id of your Custom Authorization Server. + +For more information, see the [okta docs](https://developer.okta.com/docs/concepts/auth-servers/). + +### Add provider event listener + +Configure the package's listener to listen for `SocialiteWasCalled` events. + +Add the event to your `listen[]` array in `app/Providers/EventServiceProvider`. See the [Base Installation Guide](https://socialiteproviders.com/usage/) for detailed instructions. + +```php +protected $listen = [ + \SocialiteProviders\Manager\SocialiteWasCalled::class => [ + // ... other providers + \SocialiteProviders\Okta\OktaExtendSocialite::class.'@handle', + ], +]; +``` + +### Usage + +You should now be able to use the provider like you would regularly use Socialite (assuming you have the facade installed): + +```php +return Socialite::driver('okta')->redirect(); +``` + +Store a local copy in your callback: + +```php +public function handleProviderCallback(\Illuminate\Http\Request $request) +{ + $user = Socialite::driver('okta')->user(); + $localUser = User::updateOrCreate(['email' => $user->email], [ + 'email' => $user->email, + 'name' => $user->name, + 'token' => $user->token, + 'id_token' => $user->id_token + ]); + + try { + Auth::login($localUser); + } + catch (\Throwable $e) { + return redirect('/login-okta'); + } + + return redirect('/home'); +} +``` + +Generate the logout url from your controller: + +```php +public function logout(\Illuminate\Http\Request $request) +{ + $idToken = $request->user()->id_token; + $logoutUrl = Socialite::driver('okta')->getLogoutUrl($idToken, URL::to('/')); + Auth::logout(); + + return redirect($logoutUrl); +} +``` + +#### Client Token +To obtain a client access token for authenticating to other apps without a user: + +```php +$response = (object) Socialite::driver('okta')->getClientAccessTokenResponse(); +$token = $response->access_token; +``` +NOTE: no caching of this token is performed. It's strongly suggested caching the token locally for its ttl + +### Returned User fields + +- ``id`` +- ``email`` +- ``email_verified`` +- ``nickname`` +- ``name`` +- ``first_name`` +- ``last_name`` +- ``profileUrl`` +- ``address`` +- ``phone`` diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..5c0a5b0 --- /dev/null +++ b/composer.json @@ -0,0 +1,33 @@ +{ + "name": "socialiteproviders/okta", + "description": "Okta OAuth2 Provider for Laravel Socialite", + "keywords": [ + "laravel", + "oauth", + "okta", + "provider", + "socialite" + ], + "license": "MIT", + "authors": [ + { + "name": "Chase Coney", + "email": "chase.coney@gmail.com" + } + ], + "require": { + "php": "^7.2 || ^8.0", + "ext-json": "*", + "socialiteproviders/manager": "~4.0" + }, + "autoload": { + "psr-4": { + "SocialiteProviders\\Okta\\": "" + } + }, + "support": { + "issues": "https://github.com/socialiteproviders/providers/issues", + "source": "https://github.com/socialiteproviders/providers", + "docs": "https://socialiteproviders.com/okta" + } +}