diff --git a/authentik/sources/oauth/apps.py b/authentik/sources/oauth/apps.py
index d89838476..1dbeb54e1 100644
--- a/authentik/sources/oauth/apps.py
+++ b/authentik/sources/oauth/apps.py
@@ -14,6 +14,7 @@ AUTHENTIK_SOURCES_OAUTH_TYPES = [
"authentik.sources.oauth.types.github",
"authentik.sources.oauth.types.google",
"authentik.sources.oauth.types.oidc",
+ "authentik.sources.oauth.types.okta",
"authentik.sources.oauth.types.reddit",
"authentik.sources.oauth.types.twitter",
]
diff --git a/authentik/sources/oauth/models.py b/authentik/sources/oauth/models.py
index 7276d7575..f48e2a9c7 100644
--- a/authentik/sources/oauth/models.py
+++ b/authentik/sources/oauth/models.py
@@ -183,6 +183,16 @@ class AppleOAuthSource(OAuthSource):
verbose_name_plural = _("Apple OAuth Sources")
+class OktaOAuthSource(OAuthSource):
+ """Login using a okta.com."""
+
+ class Meta:
+
+ abstract = True
+ verbose_name = _("Okta OAuth Source")
+ verbose_name_plural = _("Okta OAuth Sources")
+
+
class UserOAuthSourceConnection(UserSourceConnection):
"""Authorized remote OAuth provider."""
diff --git a/authentik/sources/oauth/types/okta.py b/authentik/sources/oauth/types/okta.py
new file mode 100644
index 000000000..5f03bda53
--- /dev/null
+++ b/authentik/sources/oauth/types/okta.py
@@ -0,0 +1,51 @@
+"""Okta OAuth Views"""
+from typing import Any
+
+from authentik.sources.oauth.models import OAuthSource
+from authentik.sources.oauth.types.azure_ad import AzureADClient
+from authentik.sources.oauth.types.manager import MANAGER, SourceType
+from authentik.sources.oauth.views.callback import OAuthCallback
+from authentik.sources.oauth.views.redirect import OAuthRedirect
+
+
+class OktaOAuthRedirect(OAuthRedirect):
+ """Okta OAuth2 Redirect"""
+
+ def get_additional_parameters(self, source: OAuthSource): # pragma: no cover
+ return {
+ "scope": "openid email profile",
+ }
+
+
+class OktaOAuth2Callback(OAuthCallback):
+ """Okta OAuth2 Callback"""
+
+ # Okta has the same quirk as azure and throws an error if the access token
+ # is set via query parameter, so we re-use the azure client
+ # see https://github.com/goauthentik/authentik/issues/1910
+ client_class = AzureADClient
+
+ def get_user_id(self, info: dict[str, str]) -> str:
+ return info.get("sub", "")
+
+ def get_user_enroll_context(
+ self,
+ info: dict[str, Any],
+ ) -> dict[str, Any]:
+ return {
+ "username": info.get("nickname"),
+ "email": info.get("email"),
+ "name": info.get("name"),
+ }
+
+
+@MANAGER.type()
+class OktaType(SourceType):
+ """Okta Type definition"""
+
+ callback_view = OktaOAuth2Callback
+ redirect_view = OktaOAuthRedirect
+ name = "Okta"
+ slug = "okta"
+
+ urls_customizable = True
diff --git a/schema.yml b/schema.yml
index 7247a8723..29522b730 100644
--- a/schema.yml
+++ b/schema.yml
@@ -28992,6 +28992,7 @@ components:
- github
- google
- openidconnect
+ - okta
- reddit
- twitter
type: string
diff --git a/web/authentik/sources/okta.svg b/web/authentik/sources/okta.svg
new file mode 100644
index 000000000..5595186b2
--- /dev/null
+++ b/web/authentik/sources/okta.svg
@@ -0,0 +1,31 @@
+
+
+
diff --git a/web/src/api/Sentry.ts b/web/src/api/Sentry.ts
index 11990cf00..8014b2e70 100644
--- a/web/src/api/Sentry.ts
+++ b/web/src/api/Sentry.ts
@@ -44,7 +44,7 @@ export function configureSentry(canDoPpi: boolean = false): Promise {
if (window.location.pathname.includes("if/")) {
// Get the interface name from URL
const pathMatches = window.location.pathname.match(/.+if\/(\w+)\//);
- let currentInterface = "unkown";
+ let currentInterface = "unknown";
if (pathMatches && pathMatches.length >= 2) {
currentInterface = pathMatches[1];
}