diff --git a/authentik/sources/oauth/apps.py b/authentik/sources/oauth/apps.py
index b4812f85d..b3d6fd67e 100644
--- a/authentik/sources/oauth/apps.py
+++ b/authentik/sources/oauth/apps.py
@@ -17,6 +17,7 @@ AUTHENTIK_SOURCES_OAUTH_TYPES = [
"authentik.sources.oauth.types.reddit",
"authentik.sources.oauth.types.twitter",
"authentik.sources.oauth.types.mailcow",
+ "authentik.sources.oauth.types.twitch",
]
diff --git a/authentik/sources/oauth/models.py b/authentik/sources/oauth/models.py
index c4a84a8c6..8b2bf90c1 100644
--- a/authentik/sources/oauth/models.py
+++ b/authentik/sources/oauth/models.py
@@ -115,6 +115,16 @@ class GitHubOAuthSource(OAuthSource):
verbose_name_plural = _("GitHub OAuth Sources")
+class TwitchOAuthSource(OAuthSource):
+ """Social Login using Twitch."""
+
+ class Meta:
+
+ abstract = True
+ verbose_name = _("Twitch OAuth Source")
+ verbose_name_plural = _("Twitch OAuth Sources")
+
+
class MailcowOAuthSource(OAuthSource):
"""Social Login using Mailcow."""
diff --git a/authentik/sources/oauth/tests/test_type_twitch.py b/authentik/sources/oauth/tests/test_type_twitch.py
new file mode 100644
index 000000000..f18e4eaf2
--- /dev/null
+++ b/authentik/sources/oauth/tests/test_type_twitch.py
@@ -0,0 +1,37 @@
+"""Twitch Type tests"""
+from django.test import TestCase
+
+from authentik.sources.oauth.models import OAuthSource
+from authentik.sources.oauth.types.twitch import TwitchOAuth2Callback
+
+# https://dev.twitch.tv/docs/authentication/getting-tokens-oidc/#getting-claims-information-from-an-access-token
+TWITCH_USER = {
+ "aud": "ym2tq9o71tikh2zyebksiture1hzg5",
+ "exp": 1665261184,
+ "iat": 1665260184,
+ "iss": "https://id.twitch.tv/oauth2",
+ "sub": "603916897",
+ "email": "foo@bar.baz",
+ "preferred_username": "FooBar",
+}
+
+
+class TestTypeTwitch(TestCase):
+ """OAuth Source tests"""
+
+ def setUp(self):
+ self.source = OAuthSource.objects.create(
+ name="test",
+ slug="test",
+ provider_type="twitch",
+ authorization_url="",
+ profile_url="",
+ consumer_key="",
+ )
+
+ def test_enroll_context(self):
+ """Test twitch Enrollment context"""
+ ak_context = TwitchOAuth2Callback().get_user_enroll_context(TWITCH_USER)
+ self.assertEqual(ak_context["username"], TWITCH_USER["preferred_username"])
+ self.assertEqual(ak_context["email"], TWITCH_USER["email"])
+ self.assertEqual(ak_context["name"], TWITCH_USER["preferred_username"])
diff --git a/authentik/sources/oauth/types/twitch.py b/authentik/sources/oauth/types/twitch.py
new file mode 100644
index 000000000..5fa9fad74
--- /dev/null
+++ b/authentik/sources/oauth/types/twitch.py
@@ -0,0 +1,60 @@
+"""Twitch OAuth Views"""
+from json import dumps
+from typing import Any, Optional
+
+from authentik.sources.oauth.clients.oauth2 import UserprofileHeaderAuthClient
+from authentik.sources.oauth.types.registry import SourceType, registry
+from authentik.sources.oauth.views.callback import OAuthCallback
+from authentik.sources.oauth.views.redirect import OAuthRedirect
+
+
+class TwitchClient(UserprofileHeaderAuthClient):
+ """Twitch needs the token_type to be capitalized for the request header."""
+
+ def get_profile_info(self, token: dict[str, str]) -> Optional[dict[str, Any]]:
+ token["token_type"] = token["token_type"].capitalize()
+ return super().get_profile_info(token)
+
+
+class TwitchOAuthRedirect(OAuthRedirect):
+ """Twitch OAuth2 Redirect"""
+
+ def get_additional_parameters(self, source): # pragma: no cover
+ claims = {"userinfo": {"email": None, "preferred_username": None}}
+ return {
+ "scope": ["openid"],
+ "claims": dumps(claims),
+ }
+
+
+class TwitchOAuth2Callback(OAuthCallback):
+ """Twitch OAuth2 Callback"""
+
+ client_class = TwitchClient
+
+ 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("preferred_username"),
+ "email": info.get("email"),
+ "name": info.get("preferred_username"),
+ }
+
+
+@registry.register()
+class TwitchType(SourceType):
+ """Twitch Type definition"""
+
+ callback_view = TwitchOAuth2Callback
+ redirect_view = TwitchOAuthRedirect
+ name = "Twitch"
+ slug = "twitch"
+
+ authorization_url = "https://id.twitch.tv/oauth2/authorize"
+ access_token_url = "https://id.twitch.tv/oauth2/token" # nosec
+ profile_url = "https://id.twitch.tv/oauth2/userinfo"
diff --git a/schema.yml b/schema.yml
index 9e495ae95..47324b0fd 100644
--- a/schema.yml
+++ b/schema.yml
@@ -35104,6 +35104,7 @@ components:
- reddit
- twitter
- mailcow
+ - twitch
type: string
ProxyMode:
enum:
diff --git a/web/authentik/sources/twitch.svg b/web/authentik/sources/twitch.svg
new file mode 100644
index 000000000..bceb32678
--- /dev/null
+++ b/web/authentik/sources/twitch.svg
@@ -0,0 +1,19 @@
+
+
+
\ No newline at end of file
diff --git a/website/integrations/sources/twitch/index.md b/website/integrations/sources/twitch/index.md
new file mode 100644
index 000000000..1e49c7a8d
--- /dev/null
+++ b/website/integrations/sources/twitch/index.md
@@ -0,0 +1,60 @@
+---
+title: Twitch
+---
+
+Support level: Community
+
+Allows users to authenticate using their Twitch credentials
+
+## Preparation
+
+The following placeholders will be used:
+
+- `authentik.company` is the FQDN of the authentik install.
+
+## Twitch
+
+1. Click **Register Your Application** in the Twitch Developers Console https://dev.twitch.tv/console
+
+![Register Your Application Button](twitch1.png)
+
+2. Name your Application
+
+3. Add https://authentik.company/source/oauth/callback/twitch in the **OAuth Redirect URLs** field
+
+4. Select a Category for your Application
+
+5. Click **Create** to finish the registration of your Application
+
+![Create Application](twitch2.png)
+
+6. Click **Manage** on your newly created Application
+
+![Manage Application](twitch3.png)
+
+7. Copy your Client ID and save it for later
+
+8. Click **New Secret** to create a new Secret
+
+9. Copy the above Secret and also save it for later
+
+![Copy Keys](twitch4.png)
+
+## authentik
+
+10. Under _Directory -> Federation & Social login_ Click **Create Twitch OAuth Source**
+
+11. **Name:** Choose a name (For the example I used Twitch)
+12. **Slug:** twitch (You can choose a different slug, if you do you will need to update the Twitch redirect URL and point it to the correct slug.)
+13. **Consumer Key:** Client ID from step 7
+14. **Consumer Secret:** Secret from step 9
+
+Here is an example of a complete authentik Twitch OAuth Source
+
+![Authentik Source Example](twitch5.png)
+
+Save, and you now have Twitch as a source.
+
+:::note
+For more details on how-to have the new source display on the Login Page see [here](../).
+:::
diff --git a/website/integrations/sources/twitch/twitch1.png b/website/integrations/sources/twitch/twitch1.png
new file mode 100644
index 000000000..d6edb5f6a
Binary files /dev/null and b/website/integrations/sources/twitch/twitch1.png differ
diff --git a/website/integrations/sources/twitch/twitch2.png b/website/integrations/sources/twitch/twitch2.png
new file mode 100644
index 000000000..281358d88
Binary files /dev/null and b/website/integrations/sources/twitch/twitch2.png differ
diff --git a/website/integrations/sources/twitch/twitch3.png b/website/integrations/sources/twitch/twitch3.png
new file mode 100644
index 000000000..bc51dd129
Binary files /dev/null and b/website/integrations/sources/twitch/twitch3.png differ
diff --git a/website/integrations/sources/twitch/twitch4.png b/website/integrations/sources/twitch/twitch4.png
new file mode 100644
index 000000000..a1a4fe238
Binary files /dev/null and b/website/integrations/sources/twitch/twitch4.png differ
diff --git a/website/integrations/sources/twitch/twitch5.png b/website/integrations/sources/twitch/twitch5.png
new file mode 100644
index 000000000..f6cbb4ed9
Binary files /dev/null and b/website/integrations/sources/twitch/twitch5.png differ
diff --git a/website/sidebarsIntegrations.js b/website/sidebarsIntegrations.js
index 772c24d23..6d71cec6c 100644
--- a/website/sidebarsIntegrations.js
+++ b/website/sidebarsIntegrations.js
@@ -137,6 +137,7 @@ module.exports = {
"sources/github/index",
"sources/google/index",
"sources/mailcow/index",
+ "sources/twitch/index",
"sources/plex/index",
"sources/twitter/index",
],