diff --git a/app/config/providers.php b/app/config/providers.php
index 9a6b5fb50f..27f63b88fb 100644
--- a/app/config/providers.php
+++ b/app/config/providers.php
@@ -141,6 +141,16 @@ return [ // Ordered by ABC.
'beta' => false,
'mock' => false,
],
+ 'okta' => [
+ 'name' => 'Okta',
+ 'developers' => 'https://developer.okta.com/',
+ 'icon' => 'icon-okta',
+ 'enabled' => true,
+ 'sandbox' => false,
+ 'form' => 'okta.phtml',
+ 'beta' => false,
+ 'mock' => false,
+ ],
'paypal' => [
'name' => 'PayPal',
'developers' => 'https://developer.paypal.com/docs/api/overview/',
diff --git a/app/views/console/users/oauth/okta.phtml b/app/views/console/users/oauth/okta.phtml
new file mode 100644
index 0000000000..44c47c6550
--- /dev/null
+++ b/app/views/console/users/oauth/okta.phtml
@@ -0,0 +1,12 @@
+getParam('provider', '');
+?>
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/public/images/users/okta.png b/public/images/users/okta.png
new file mode 100644
index 0000000000..c66b273cfd
Binary files /dev/null and b/public/images/users/okta.png differ
diff --git a/public/scripts/views/forms/oauth-custom.js b/public/scripts/views/forms/oauth-custom.js
index fd8bd855d0..fabb2b17ee 100644
--- a/public/scripts/views/forms/oauth-custom.js
+++ b/public/scripts/views/forms/oauth-custom.js
@@ -16,6 +16,10 @@
"keyID": "oauth2AppleKeyId",
"teamID": "oauth2AppleTeamId",
"p8": "oauth2AppleP8"
+ },
+ "Okta": {
+ "clientSecret": "oauth2OktaClientSecret",
+ "oktaDomain": "oauth2OktaDomain"
}
}
let provider = element.getAttribute("data-forms-oauth-custom");
diff --git a/src/Appwrite/Auth/OAuth2/Okta.php b/src/Appwrite/Auth/OAuth2/Okta.php
new file mode 100644
index 0000000000..70ea0d4b13
--- /dev/null
+++ b/src/Appwrite/Auth/OAuth2/Okta.php
@@ -0,0 +1,210 @@
+getOktaDomain().'/oauth2/default/v1/authorize?'.\http_build_query([
+ 'client_id' => $this->appID,
+ 'redirect_uri' => $this->callback,
+ 'state'=> \json_encode($this->state),
+ 'scope'=> \implode(' ', $this->getScopes()),
+ 'response_type' => 'code'
+ ]);
+ }
+
+ /**
+ * @param string $code
+ *
+ * @return array
+ */
+ protected function getTokens(string $code): array
+ {
+ if(empty($this->tokens)) {
+ $headers = ['Content-Type: application/x-www-form-urlencoded'];
+ $this->tokens = \json_decode($this->request(
+ 'POST',
+ 'https://'.$this->getOktaDomain().'/oauth2/default/v1/token',
+ $headers,
+ \http_build_query([
+ 'code' => $code,
+ 'client_id' => $this->appID,
+ 'client_secret' => $this->getClientSecret(),
+ 'redirect_uri' => $this->callback,
+ 'scope' => \implode(' ', $this->getScopes()),
+ 'grant_type' => 'authorization_code'
+ ])
+ ), true);
+ }
+
+ return $this->tokens;
+ }
+
+
+ /**
+ * @param string $refreshToken
+ *
+ * @return array
+ */
+ public function refreshTokens(string $refreshToken): array
+ {
+ $headers = ['Content-Type: application/x-www-form-urlencoded'];
+ $this->tokens = \json_decode($this->request(
+ 'POST',
+ 'https://'.$this->getOktaDomain().'/oauth2/default/v1/token',
+ $headers,
+ \http_build_query([
+ 'refresh_token' => $refreshToken,
+ 'client_id' => $this->appID,
+ 'client_secret' => $this->getClientSecret(),
+ 'grant_type' => 'refresh_token'
+ ])
+ ), true);
+
+ if(empty($this->tokens['refresh_token'])) {
+ $this->tokens['refresh_token'] = $refreshToken;
+ }
+
+ return $this->tokens;
+ }
+
+ /**
+ * @param string $accessToken
+ *
+ * @return string
+ */
+ public function getUserID(string $accessToken): string
+ {
+ $user = $this->getUser($accessToken);
+
+ if (isset($user['sub'])) {
+ return $user['sub'];
+ }
+
+ return '';
+ }
+
+ /**
+ * @param string $accessToken
+ *
+ * @return string
+ */
+ public function getUserEmail(string $accessToken): string
+ {
+ $user = $this->getUser($accessToken);
+
+ if (isset($user['email'])) {
+ return $user['email'];
+ }
+
+ return '';
+ }
+
+ /**
+ * @param string $accessToken
+ *
+ * @return string
+ */
+ public function getUserName(string $accessToken): string
+ {
+ $user = $this->getUser($accessToken);
+
+ if (isset($user['name'])) {
+ return $user['name'];
+ }
+
+ return '';
+ }
+
+ /**
+ * @param string $accessToken
+ *
+ * @return array
+ */
+ protected function getUser(string $accessToken): array
+ {
+ if (empty($this->user)) {
+ $headers = ['Authorization: Bearer '. \urlencode($accessToken)];
+ $user = $this->request('GET', 'https://'.$this->getOktaDomain().'/v1/userinfo', $headers);
+ $this->user = \json_decode($user, true);
+ }
+
+ return $this->user;
+ }
+
+ /**
+ * Extracts the Client Secret from the JSON stored in appSecret
+ *
+ * @return string
+ */
+ protected function getClientSecret(): string
+ {
+ $secret = $this->getAppSecret();
+
+ return (isset($secret['clientSecret'])) ? $secret['clientSecret'] : '';
+ }
+
+ /**
+ * Extracts the Okta Domain from the JSON stored in appSecret
+ *
+ * @return string
+ */
+ protected function getOktaDomain(): string
+ {
+ $secret = $this->getAppSecret();
+ return (isset($secret['oktaDomain'])) ? $secret['oktaDomain'] : '';
+ }
+
+ /**
+ * Decode the JSON stored in appSecret
+ *
+ * @return array
+ */
+ protected function getAppSecret(): array
+ {
+ try {
+ $secret = \json_decode($this->appSecret, true, 512, JSON_THROW_ON_ERROR);
+ } catch (\Throwable $th) {
+ throw new \Exception('Invalid secret');
+ }
+ return $secret;
+ }
+}
\ No newline at end of file