sources/oauth: add Sign in with Apple (#1635)

* sources/oauth: add apple sign in support

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* website/docs: apple sign in docs

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* website/docs: fix missing apple in sidebar

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* sources/oauth: add fallback values for name and slug

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens L 2021-10-18 16:35:12 +02:00 committed by GitHub
parent 2c06eed8e7
commit 922fc9b8d5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 313 additions and 98 deletions

View File

@ -7,14 +7,15 @@ from structlog.stdlib import get_logger
LOGGER = get_logger() LOGGER = get_logger()
AUTHENTIK_SOURCES_OAUTH_TYPES = [ AUTHENTIK_SOURCES_OAUTH_TYPES = [
"authentik.sources.oauth.types.apple",
"authentik.sources.oauth.types.azure_ad",
"authentik.sources.oauth.types.discord", "authentik.sources.oauth.types.discord",
"authentik.sources.oauth.types.facebook", "authentik.sources.oauth.types.facebook",
"authentik.sources.oauth.types.github", "authentik.sources.oauth.types.github",
"authentik.sources.oauth.types.google", "authentik.sources.oauth.types.google",
"authentik.sources.oauth.types.oidc",
"authentik.sources.oauth.types.reddit", "authentik.sources.oauth.types.reddit",
"authentik.sources.oauth.types.twitter", "authentik.sources.oauth.types.twitter",
"authentik.sources.oauth.types.azure_ad",
"authentik.sources.oauth.types.oidc",
] ]

View File

@ -1,6 +1,6 @@
"""OAuth Clients""" """OAuth Clients"""
from typing import Any, Optional from typing import Any, Optional
from urllib.parse import urlencode from urllib.parse import quote, urlencode
from django.http import HttpRequest from django.http import HttpRequest
from requests import Session from requests import Session
@ -58,7 +58,7 @@ class BaseOAuthClient:
args = self.get_redirect_args() args = self.get_redirect_args()
additional = parameters or {} additional = parameters or {}
args.update(additional) args.update(additional)
params = urlencode(args) params = urlencode(args, quote_via=quote)
LOGGER.info("redirect args", **args) LOGGER.info("redirect args", **args)
authorization_url = self.source.type.authorization_url or "" authorization_url = self.source.type.authorization_url or ""
if self.source.type.urls_customizable and self.source.authorization_url: if self.source.type.urls_customizable and self.source.authorization_url:

View File

@ -20,10 +20,16 @@ class OAuth2Client(BaseOAuthClient):
"Accept": "application/json", "Accept": "application/json",
} }
def get_request_arg(self, key: str, default: Optional[Any] = None) -> Any:
"""Depending on request type, get data from post or get"""
if self.request.method == "POST":
return self.request.POST.get(key, default)
return self.request.GET.get(key, default)
def check_application_state(self) -> bool: def check_application_state(self) -> bool:
"Check optional state parameter." "Check optional state parameter."
stored = self.request.session.get(self.session_key, None) stored = self.request.session.get(self.session_key, None)
returned = self.request.GET.get("state", None) returned = self.get_request_arg("state", None)
check = False check = False
if stored is not None: if stored is not None:
if returned is not None: if returned is not None:
@ -38,23 +44,31 @@ class OAuth2Client(BaseOAuthClient):
"Generate state optional parameter." "Generate state optional parameter."
return get_random_string(32) return get_random_string(32)
def get_client_id(self) -> str:
"""Get client id"""
return self.source.consumer_key
def get_client_secret(self) -> str:
"""Get client secret"""
return self.source.consumer_secret
def get_access_token(self, **request_kwargs) -> Optional[dict[str, Any]]: def get_access_token(self, **request_kwargs) -> Optional[dict[str, Any]]:
"Fetch access token from callback request." "Fetch access token from callback request."
callback = self.request.build_absolute_uri(self.callback or self.request.path) callback = self.request.build_absolute_uri(self.callback or self.request.path)
if not self.check_application_state(): if not self.check_application_state():
LOGGER.warning("Application state check failed.") LOGGER.warning("Application state check failed.")
return None return None
if "code" in self.request.GET: code = self.get_request_arg("code", None)
args = { if not code:
"client_id": self.source.consumer_key,
"redirect_uri": callback,
"client_secret": self.source.consumer_secret,
"code": self.request.GET["code"],
"grant_type": "authorization_code",
}
else:
LOGGER.warning("No code returned by the source") LOGGER.warning("No code returned by the source")
return None return None
args = {
"client_id": self.get_client_id(),
"client_secret": self.get_client_secret(),
"redirect_uri": callback,
"code": code,
"grant_type": "authorization_code",
}
try: try:
access_token_url = self.source.type.access_token_url or "" access_token_url = self.source.type.access_token_url or ""
if self.source.type.urls_customizable and self.source.access_token_url: if self.source.type.urls_customizable and self.source.access_token_url:
@ -75,7 +89,7 @@ class OAuth2Client(BaseOAuthClient):
def get_redirect_args(self) -> dict[str, str]: def get_redirect_args(self) -> dict[str, str]:
"Get request parameters for redirect url." "Get request parameters for redirect url."
callback = self.request.build_absolute_uri(self.callback) callback = self.request.build_absolute_uri(self.callback)
client_id: str = self.source.consumer_key client_id: str = self.get_client_id()
args: dict[str, str] = { args: dict[str, str] = {
"client_id": client_id, "client_id": client_id,
"redirect_uri": callback, "redirect_uri": callback,

View File

@ -2,7 +2,6 @@
from typing import TYPE_CHECKING, Optional, Type from typing import TYPE_CHECKING, Optional, Type
from django.db import models from django.db import models
from django.templatetags.static import static
from django.urls import reverse from django.urls import reverse
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from rest_framework.serializers import Serializer from rest_framework.serializers import Serializer
@ -49,7 +48,7 @@ class OAuthSource(Source):
consumer_secret = models.TextField() consumer_secret = models.TextField()
@property @property
def type(self) -> "SourceType": def type(self) -> Type["SourceType"]:
"""Return the provider instance for this source""" """Return the provider instance for this source"""
from authentik.sources.oauth.types.manager import MANAGER from authentik.sources.oauth.types.manager import MANAGER
@ -67,6 +66,7 @@ class OAuthSource(Source):
@property @property
def ui_login_button(self) -> UILoginButton: def ui_login_button(self) -> UILoginButton:
provider_type = self.type
return UILoginButton( return UILoginButton(
challenge=RedirectChallenge( challenge=RedirectChallenge(
instance={ instance={
@ -77,7 +77,7 @@ class OAuthSource(Source):
), ),
} }
), ),
icon_url=static(f"authentik/sources/{self.provider_type}.svg"), icon_url=provider_type().icon_url(),
name=self.name, name=self.name,
) )
@ -173,6 +173,16 @@ class OpenIDConnectOAuthSource(OAuthSource):
verbose_name_plural = _("OpenID OAuth Sources") verbose_name_plural = _("OpenID OAuth Sources")
class AppleOAuthSource(OAuthSource):
"""Login using a apple.com."""
class Meta:
abstract = True
verbose_name = _("Apple OAuth Source")
verbose_name_plural = _("Apple OAuth Sources")
class UserOAuthSourceConnection(UserSourceConnection): class UserOAuthSourceConnection(UserSourceConnection):
"""Authorized remote OAuth provider.""" """Authorized remote OAuth provider."""

View File

@ -0,0 +1,102 @@
"""Apple OAuth Views"""
from base64 import b64decode
from json import loads
from time import time
from typing import Any, Optional
from jwt import encode
from structlog.stdlib import get_logger
from authentik.sources.oauth.clients.oauth2 import OAuth2Client
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
LOGGER = get_logger()
class AppleOAuthClient(OAuth2Client):
"""Apple OAuth2 client"""
def get_client_id(self) -> str:
parts = self.source.consumer_key.split(";")
if len(parts) < 3:
return self.source.consumer_key
return parts[0]
def get_client_secret(self) -> str:
now = time()
parts = self.source.consumer_key.split(";")
if len(parts) < 3:
raise ValueError(
(
"Apple Source client_id should be formatted like "
"services_id_identifier;apple_team_id;key_id"
)
)
LOGGER.debug("got values from client_id", team=parts[1], kid=parts[2])
payload = {
"iss": parts[1],
"iat": now,
"exp": now + 86400 * 180,
"aud": "https://appleid.apple.com",
"sub": self.source.consumer_key,
}
# pyright: reportGeneralTypeIssues=false
jwt = encode(payload, self.source.consumer_secret, "ES256", {"kid": parts[2]})
LOGGER.debug("signing payload as secret key", payload=payload, jwt=jwt)
return jwt
def get_profile_info(self, token: dict[str, str]) -> Optional[dict[str, Any]]:
id_token = token.get("id_token")
_, raw_payload, _ = id_token.split(".")
payload = loads(b64decode(raw_payload.encode().decode()))
return payload
class AppleOAuthRedirect(OAuthRedirect):
"""Apple OAuth2 Redirect"""
client_class = AppleOAuthClient
def get_additional_parameters(self, source): # pragma: no cover
return {
"scope": "name email",
"response_mode": "form_post",
}
class AppleOAuth2Callback(OAuthCallback):
"""Apple OAuth2 Callback"""
client_class = AppleOAuthClient
def get_user_id(self, info: dict[str, Any]) -> Optional[str]:
return info["sub"]
def get_user_enroll_context(
self,
info: dict[str, Any],
) -> dict[str, Any]:
print(info)
return {
"email": info.get("email"),
"name": info.get("name"),
}
@MANAGER.type()
class AppleType(SourceType):
"""Apple Type definition"""
callback_view = AppleOAuth2Callback
redirect_view = AppleOAuthRedirect
name = "Apple"
slug = "apple"
authorization_url = "https://appleid.apple.com/auth/authorize"
access_token_url = "https://appleid.apple.com/auth/token" # nosec
profile_url = ""
def icon_url(self) -> str:
return "https://appleid.cdn-apple.com/appleid/button/logo"

View File

@ -1,7 +1,8 @@
"""Source type manager""" """Source type manager"""
from enum import Enum from enum import Enum
from typing import Callable, Optional from typing import Callable, Optional, Type
from django.templatetags.static import static
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
from authentik.sources.oauth.views.callback import OAuthCallback from authentik.sources.oauth.views.callback import OAuthCallback
@ -22,8 +23,8 @@ class SourceType:
callback_view = OAuthCallback callback_view = OAuthCallback
redirect_view = OAuthRedirect redirect_view = OAuthRedirect
name: str name: str = "default"
slug: str slug: str = "default"
urls_customizable = False urls_customizable = False
@ -32,12 +33,16 @@ class SourceType:
access_token_url: Optional[str] = None access_token_url: Optional[str] = None
profile_url: Optional[str] = None profile_url: Optional[str] = None
def icon_url(self) -> str:
"""Get Icon URL for login"""
return static(f"authentik/sources/{self.slug}.svg")
class SourceTypeManager: class SourceTypeManager:
"""Manager to hold all Source types.""" """Manager to hold all Source types."""
def __init__(self) -> None: def __init__(self) -> None:
self.__sources: list[SourceType] = [] self.__sources: list[Type[SourceType]] = []
def type(self): def type(self):
"""Class decorator to register classes inline.""" """Class decorator to register classes inline."""
@ -56,14 +61,14 @@ class SourceTypeManager:
"""Get list of tuples of all registered names""" """Get list of tuples of all registered names"""
return [(x.slug, x.name) for x in self.__sources] return [(x.slug, x.name) for x in self.__sources]
def find_type(self, type_name: str) -> SourceType: def find_type(self, type_name: str) -> Type[SourceType]:
"""Find type based on source""" """Find type based on source"""
found_type = None found_type = None
for src_type in self.__sources: for src_type in self.__sources:
if src_type.slug == type_name: if src_type.slug == type_name:
return src_type return src_type
if not found_type: if not found_type:
found_type = SourceType() found_type = SourceType
LOGGER.warning( LOGGER.warning(
"no matching type found, using default", "no matching type found, using default",
wanted=type_name, wanted=type_name,

View File

@ -24,7 +24,7 @@ class OAuthCallback(OAuthClientMixin, View):
source: OAuthSource source: OAuthSource
# pylint: disable=too-many-return-statements # pylint: disable=too-many-return-statements
def get(self, request: HttpRequest, *_, **kwargs) -> HttpResponse: def dispatch(self, request: HttpRequest, *_, **kwargs) -> HttpResponse:
"""View Get handler""" """View Get handler"""
slug = kwargs.get("source_slug", "") slug = kwargs.get("source_slug", "")
try: try:

View File

@ -1,6 +1,8 @@
"""Dispatch OAuth views to respective views""" """Dispatch OAuth views to respective views"""
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.utils.decorators import method_decorator
from django.views import View from django.views import View
from django.views.decorators.csrf import csrf_exempt
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
from authentik.sources.oauth.models import OAuthSource from authentik.sources.oauth.models import OAuthSource
@ -9,6 +11,7 @@ from authentik.sources.oauth.types.manager import MANAGER, RequestKind
LOGGER = get_logger() LOGGER = get_logger()
@method_decorator(csrf_exempt, name="dispatch")
class DispatcherView(View): class DispatcherView(View):
"""Dispatch OAuth Redirect/Callback views to their proper class based on URL parameters""" """Dispatch OAuth Redirect/Callback views to their proper class based on URL parameters"""

View File

@ -119,7 +119,7 @@ class TestIdentificationStage(APITestCase):
"to": "/source/oauth/login/test/", "to": "/source/oauth/login/test/",
"type": ChallengeTypes.REDIRECT.value, "type": ChallengeTypes.REDIRECT.value,
}, },
"icon_url": "/static/authentik/sources/.svg", "icon_url": "/static/authentik/sources/default.svg",
"name": "test", "name": "test",
} }
], ],
@ -170,7 +170,7 @@ class TestIdentificationStage(APITestCase):
"to": "/source/oauth/login/test/", "to": "/source/oauth/login/test/",
"type": ChallengeTypes.REDIRECT.value, "type": ChallengeTypes.REDIRECT.value,
}, },
"icon_url": "/static/authentik/sources/.svg", "icon_url": "/static/authentik/sources/default.svg",
"name": "test", "name": "test",
} }
], ],
@ -226,7 +226,7 @@ class TestIdentificationStage(APITestCase):
}, },
"sources": [ "sources": [
{ {
"icon_url": "/static/authentik/sources/.svg", "icon_url": "/static/authentik/sources/default.svg",
"name": "test", "name": "test",
"challenge": { "challenge": {
"component": "xak-flow-redirect", "component": "xak-flow-redirect",
@ -280,7 +280,7 @@ class TestIdentificationStage(APITestCase):
"to": "/source/oauth/login/test/", "to": "/source/oauth/login/test/",
"type": ChallengeTypes.REDIRECT.value, "type": ChallengeTypes.REDIRECT.value,
}, },
"icon_url": "/static/authentik/sources/.svg", "icon_url": "/static/authentik/sources/default.svg",
"name": "test", "name": "test",
} }
], ],

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-10-11 14:12+0000\n" "POT-Creation-Date: 2021-10-18 10:40+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -308,6 +308,10 @@ msgstr ""
msgid "Notification Webhook Mappings" msgid "Notification Webhook Mappings"
msgstr "" msgstr ""
#: authentik/events/monitored_tasks.py:122
msgid "Task has not been run yet."
msgstr ""
#: authentik/flows/api/flows.py:350 #: authentik/flows/api/flows.py:350
#, python-format #, python-format
msgid "Flow not applicable to current user/request: %(messages)s" msgid "Flow not applicable to current user/request: %(messages)s"
@ -385,33 +389,33 @@ msgstr ""
msgid "Invalid kubeconfig" msgid "Invalid kubeconfig"
msgstr "" msgstr ""
#: authentik/outposts/models.py:164 #: authentik/outposts/models.py:167
msgid "Outpost Service-Connection" msgid "Outpost Service-Connection"
msgstr "" msgstr ""
#: authentik/outposts/models.py:165 #: authentik/outposts/models.py:168
msgid "Outpost Service-Connections" msgid "Outpost Service-Connections"
msgstr "" msgstr ""
#: authentik/outposts/models.py:201 #: authentik/outposts/models.py:204
msgid "" msgid ""
"Certificate/Key used for authentication. Can be left empty for no " "Certificate/Key used for authentication. Can be left empty for no "
"authentication." "authentication."
msgstr "" msgstr ""
#: authentik/outposts/models.py:243 #: authentik/outposts/models.py:246
msgid "Docker Service-Connection" msgid "Docker Service-Connection"
msgstr "" msgstr ""
#: authentik/outposts/models.py:244 #: authentik/outposts/models.py:247
msgid "Docker Service-Connections" msgid "Docker Service-Connections"
msgstr "" msgstr ""
#: authentik/outposts/models.py:290 #: authentik/outposts/models.py:293
msgid "Kubernetes Service-Connection" msgid "Kubernetes Service-Connection"
msgstr "" msgstr ""
#: authentik/outposts/models.py:291 #: authentik/outposts/models.py:294
msgid "Kubernetes Service-Connections" msgid "Kubernetes Service-Connections"
msgstr "" msgstr ""
@ -988,108 +992,116 @@ msgstr ""
msgid "Password does not match Active Directory Complexity." msgid "Password does not match Active Directory Complexity."
msgstr "" msgstr ""
#: authentik/sources/oauth/models.py:25 #: authentik/sources/oauth/models.py:24
msgid "Request Token URL" msgid "Request Token URL"
msgstr "" msgstr ""
#: authentik/sources/oauth/models.py:27 #: authentik/sources/oauth/models.py:26
msgid "" msgid ""
"URL used to request the initial token. This URL is only required for OAuth 1." "URL used to request the initial token. This URL is only required for OAuth 1."
msgstr "" msgstr ""
#: authentik/sources/oauth/models.py:33 #: authentik/sources/oauth/models.py:32
msgid "Authorization URL" msgid "Authorization URL"
msgstr "" msgstr ""
#: authentik/sources/oauth/models.py:34 #: authentik/sources/oauth/models.py:33
msgid "URL the user is redirect to to conest the flow." msgid "URL the user is redirect to to conest the flow."
msgstr "" msgstr ""
#: authentik/sources/oauth/models.py:39 #: authentik/sources/oauth/models.py:38
msgid "Access Token URL" msgid "Access Token URL"
msgstr "" msgstr ""
#: authentik/sources/oauth/models.py:40 #: authentik/sources/oauth/models.py:39
msgid "URL used by authentik to retrieve tokens." msgid "URL used by authentik to retrieve tokens."
msgstr "" msgstr ""
#: authentik/sources/oauth/models.py:45 #: authentik/sources/oauth/models.py:44
msgid "Profile URL" msgid "Profile URL"
msgstr "" msgstr ""
#: authentik/sources/oauth/models.py:46 #: authentik/sources/oauth/models.py:45
msgid "URL used by authentik to get user information." msgid "URL used by authentik to get user information."
msgstr "" msgstr ""
#: authentik/sources/oauth/models.py:102 #: authentik/sources/oauth/models.py:101
msgid "OAuth Source" msgid "OAuth Source"
msgstr "" msgstr ""
#: authentik/sources/oauth/models.py:103 #: authentik/sources/oauth/models.py:102
msgid "OAuth Sources" msgid "OAuth Sources"
msgstr "" msgstr ""
#: authentik/sources/oauth/models.py:112 #: authentik/sources/oauth/models.py:111
msgid "GitHub OAuth Source" msgid "GitHub OAuth Source"
msgstr "" msgstr ""
#: authentik/sources/oauth/models.py:113 #: authentik/sources/oauth/models.py:112
msgid "GitHub OAuth Sources" msgid "GitHub OAuth Sources"
msgstr "" msgstr ""
#: authentik/sources/oauth/models.py:122 #: authentik/sources/oauth/models.py:121
msgid "Twitter OAuth Source" msgid "Twitter OAuth Source"
msgstr "" msgstr ""
#: authentik/sources/oauth/models.py:123 #: authentik/sources/oauth/models.py:122
msgid "Twitter OAuth Sources" msgid "Twitter OAuth Sources"
msgstr "" msgstr ""
#: authentik/sources/oauth/models.py:132 #: authentik/sources/oauth/models.py:131
msgid "Facebook OAuth Source" msgid "Facebook OAuth Source"
msgstr "" msgstr ""
#: authentik/sources/oauth/models.py:133 #: authentik/sources/oauth/models.py:132
msgid "Facebook OAuth Sources" msgid "Facebook OAuth Sources"
msgstr "" msgstr ""
#: authentik/sources/oauth/models.py:142 #: authentik/sources/oauth/models.py:141
msgid "Discord OAuth Source" msgid "Discord OAuth Source"
msgstr "" msgstr ""
#: authentik/sources/oauth/models.py:143 #: authentik/sources/oauth/models.py:142
msgid "Discord OAuth Sources" msgid "Discord OAuth Sources"
msgstr "" msgstr ""
#: authentik/sources/oauth/models.py:152 #: authentik/sources/oauth/models.py:151
msgid "Google OAuth Source" msgid "Google OAuth Source"
msgstr "" msgstr ""
#: authentik/sources/oauth/models.py:153 #: authentik/sources/oauth/models.py:152
msgid "Google OAuth Sources" msgid "Google OAuth Sources"
msgstr "" msgstr ""
#: authentik/sources/oauth/models.py:162 #: authentik/sources/oauth/models.py:161
msgid "Azure AD OAuth Source" msgid "Azure AD OAuth Source"
msgstr "" msgstr ""
#: authentik/sources/oauth/models.py:163 #: authentik/sources/oauth/models.py:162
msgid "Azure AD OAuth Sources" msgid "Azure AD OAuth Sources"
msgstr "" msgstr ""
#: authentik/sources/oauth/models.py:172 #: authentik/sources/oauth/models.py:171
msgid "OpenID OAuth Source" msgid "OpenID OAuth Source"
msgstr "" msgstr ""
#: authentik/sources/oauth/models.py:173 #: authentik/sources/oauth/models.py:172
msgid "OpenID OAuth Sources" msgid "OpenID OAuth Sources"
msgstr "" msgstr ""
#: authentik/sources/oauth/models.py:188 #: authentik/sources/oauth/models.py:181
msgid "Apple OAuth Source"
msgstr ""
#: authentik/sources/oauth/models.py:182
msgid "Apple OAuth Sources"
msgstr ""
#: authentik/sources/oauth/models.py:197
msgid "User OAuth Source Connection" msgid "User OAuth Source Connection"
msgstr "" msgstr ""
#: authentik/sources/oauth/models.py:189 #: authentik/sources/oauth/models.py:198
msgid "User OAuth Source Connections" msgid "User OAuth Source Connections"
msgstr "" msgstr ""
@ -1214,19 +1226,19 @@ msgstr ""
msgid "Duo Devices" msgid "Duo Devices"
msgstr "" msgstr ""
#: authentik/stages/authenticator_sms/models.py:97 #: authentik/stages/authenticator_sms/models.py:158
msgid "SMS Authenticator Setup Stage" msgid "SMS Authenticator Setup Stage"
msgstr "" msgstr ""
#: authentik/stages/authenticator_sms/models.py:98 #: authentik/stages/authenticator_sms/models.py:159
msgid "SMS Authenticator Setup Stages" msgid "SMS Authenticator Setup Stages"
msgstr "" msgstr ""
#: authentik/stages/authenticator_sms/models.py:116 #: authentik/stages/authenticator_sms/models.py:176
msgid "SMS Device" msgid "SMS Device"
msgstr "" msgstr ""
#: authentik/stages/authenticator_sms/models.py:117 #: authentik/stages/authenticator_sms/models.py:177
msgid "SMS Devices" msgid "SMS Devices"
msgstr "" msgstr ""
@ -1291,19 +1303,19 @@ msgstr ""
msgid "Authenticator Validation Stages" msgid "Authenticator Validation Stages"
msgstr "" msgstr ""
#: authentik/stages/authenticator_webauthn/models.py:49 #: authentik/stages/authenticator_webauthn/models.py:51
msgid "WebAuthn Authenticator Setup Stage" msgid "WebAuthn Authenticator Setup Stage"
msgstr "" msgstr ""
#: authentik/stages/authenticator_webauthn/models.py:50 #: authentik/stages/authenticator_webauthn/models.py:52
msgid "WebAuthn Authenticator Setup Stages" msgid "WebAuthn Authenticator Setup Stages"
msgstr "" msgstr ""
#: authentik/stages/authenticator_webauthn/models.py:78 #: authentik/stages/authenticator_webauthn/models.py:85
msgid "WebAuthn Device" msgid "WebAuthn Device"
msgstr "" msgstr ""
#: authentik/stages/authenticator_webauthn/models.py:79 #: authentik/stages/authenticator_webauthn/models.py:86
msgid "WebAuthn Devices" msgid "WebAuthn Devices"
msgstr "" msgstr ""

View File

@ -43,7 +43,7 @@ class OAUth1Type(SourceType):
urls_customizable = False urls_customizable = False
SOURCE_TYPE_MOCK = Mock(return_value=OAUth1Type()) SOURCE_TYPE_MOCK = Mock(return_value=OAUth1Type)
@skipUnless(platform.startswith("linux"), "requires local docker") @skipUnless(platform.startswith("linux"), "requires local docker")

View File

@ -252,7 +252,7 @@ export class OAuthSourceForm extends ModelForm<OAuthSource, string> {
?writeOnly=${this.instance !== undefined} ?writeOnly=${this.instance !== undefined}
name="consumerSecret" name="consumerSecret"
> >
<input type="text" value="" class="pf-c-form-control" required /> <textarea class="pf-c-form-control"></textarea>
</ak-form-element-horizontal> </ak-form-element-horizontal>
</div> </div>
</ak-form-group> </ak-form-group>

View File

@ -50,7 +50,7 @@ In authentik, create an application which uses this provider. Optionally apply a
### Step 3 ### Step 3
Obtain your Metadata URL from Authentik. Obtain your Metadata URL from authentik.
1. Click on the BookStack Provider 1. Click on the BookStack Provider
2. Click the Metadata Tab 2. Click the Metadata Tab
@ -69,7 +69,7 @@ Modify the following Example SAML config and paste incorporate into your `.env`
AUTH_METHOD=saml2 AUTH_METHOD=saml2
# Set the display name to be shown on the login button. # Set the display name to be shown on the login button.
# (Login with <name>) # (Login with <name>)
SAML2_NAME=Authentik SAML2_NAME=authentik
# Name of the attribute which provides the user's email address # Name of the attribute which provides the user's email address
SAML2_EMAIL_ATTRIBUTE=email SAML2_EMAIL_ATTRIBUTE=email
# Name of the attribute to use as an ID for the SAML user. # Name of the attribute to use as an ID for the SAML user.

View File

@ -21,7 +21,7 @@ The following placeholders will be used:
- `port.company` is the FQDN of Portainer. - `port.company` is the FQDN of Portainer.
- `authentik.company` is the FQDN of authentik. - `authentik.company` is the FQDN of authentik.
### Step 1 - Authentik ### Step 1 - authentik
In authentik, under _Providers_, create an _OAuth2/OpenID Provider_ with these settings: In authentik, under _Providers_, create an _OAuth2/OpenID Provider_ with these settings:
@ -57,7 +57,7 @@ Portainer by default shows commas between each item in the Scopes field. Do **N
![](./port1.png) ![](./port1.png)
### Step 3 - Authentik ### Step 3 - authentik
In authentik, create an application which uses this provider. Optionally apply access restrictions to the application using policy bindings. In authentik, create an application which uses this provider. Optionally apply access restrictions to the application using policy bindings.

View File

@ -76,9 +76,9 @@ auth:
# The auth url to send users to if they want to authenticate using OpenID Connect. # The auth url to send users to if they want to authenticate using OpenID Connect.
authurl: https://authentik.company/application/o/vikunja/ authurl: https://authentik.company/application/o/vikunja/
# The client ID used to authenticate Vikunja at the OpenID Connect provider. # The client ID used to authenticate Vikunja at the OpenID Connect provider.
clientid: THIS IS THE CLIENT ID YOU COPIED FROM STEP 1 in Authentik clientid: THIS IS THE CLIENT ID YOU COPIED FROM STEP 1 in authentik
# The client secret used to authenticate Vikunja at the OpenID Connect provider. # The client secret used to authenticate Vikunja at the OpenID Connect provider.
clientsecret: THIS IS THE CLIENT SECRET YOU COPIED FROM STEP 1 in Authentik clientsecret: THIS IS THE CLIENT SECRET YOU COPIED FROM STEP 1 in authentik
``` ```
:::note :::note

View File

@ -39,7 +39,7 @@ import TabItem from '@theme/TabItem';
{label: 'Standalone', value: 'standalone'}, {label: 'Standalone', value: 'standalone'},
]}> ]}>
<TabItem value="docker"> <TabItem value="docker">
If your Wekan is running in docker, add the following environment variables for Authentik If your Wekan is running in docker, add the following environment variables for authentik
```yaml ```yaml
environment: environment:
@ -62,7 +62,7 @@ environment:
edit `.env` and add the following: edit `.env` and add the following:
```ini ```ini
# Authentik OAUTH Config # authentik OAUTH Config
OAUTH2_ENABLED='true' OAUTH2_ENABLED='true'
OAUTH2_LOGIN_STYLE='redirect' OAUTH2_LOGIN_STYLE='redirect'
OAUTH2_CLIENT_ID='<Client ID from above>' OAUTH2_CLIENT_ID='<Client ID from above>'

View File

@ -21,7 +21,7 @@ The following placeholders will be used:
- `wp.company` is the FQDN of Wordpress. - `wp.company` is the FQDN of Wordpress.
- `authentik.company` is the FQDN of authentik. - `authentik.company` is the FQDN of authentik.
### Step 1 - Authentik ### Step 1 - authentik
In authentik, under _Providers_, create an _OAuth2/OpenID Provider_ with these settings: In authentik, under _Providers_, create an _OAuth2/OpenID Provider_ with these settings:
@ -63,7 +63,7 @@ Only settings that have been modified from default have been listed.
Review each setting and choose the ones that you require for your installation. Examples of popular settings are _Link Existing Users_, _Create user if does not exist_, and _Enforce Privacy_ Review each setting and choose the ones that you require for your installation. Examples of popular settings are _Link Existing Users_, _Create user if does not exist_, and _Enforce Privacy_
::: :::
### Step 3 - Authentik ### Step 3 - authentik
In authentik, create an application which uses this provider. Optionally apply access restrictions to the application using policy bindings. In authentik, create an application which uses this provider. Optionally apply access restrictions to the application using policy bindings.

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

View File

@ -0,0 +1,67 @@
---
title: Apple
---
Allows users to authenticate using their Apple ID.
## Preparation
:::warning
An Apple developer account is required for this.
:::
The following placeholders will be used:
- `authentik.company` is the FQDN of the authentik install.
## Apple
1. Log into your Apple developer account, and navigate to **Certificates, IDs & Profiles**, then click **Identifiers** in the sidebar.
2. Register a new Identifier with the type of **App IDs**, and the subtype **App**.
3. Choose a name that users will recognise for the **Description** field.
4. For your bundle ID, use the reverse domain of authentik, in this case `company.authentik`.
5. Scroll down the list of capabilities, and check the box next to **Sign In with Apple**.
6. At the top, click **Continue** and **Register**.
![](app_id.png)
7. Register another new Identifier with the type of **Services IDs**.
8. Again, choose the same name as above for your **Description** field.
9. Use the same identifier as above, but add a suffix like `signin` or `oauth`, as identifiers are unique.
10. At the top, click **Continue** and **Register**.
![](service_id.png)
11. Once back at the overview list, click on the just-created Identifier.
12. Enable the checkbox next to **Sign In with Apple**, and click **Configure**
13. Under domains, enter `authentik.company`.
14. Under **Return URLs**, enter `https://authentik.company/source/oauth/callback/apple/`.
![](app_service_config.png)
15. Click on **Keys** in the sidebar. Register a new Key with any name, and select **Sign in with Apple**.
16. Click on **Configure**, and select the App ID you've created above.
17. At the top, click **Save**, **Continue** and **Register**.
18. Download the Key file and note the **Key ID**.
![](key.png)
19. Note the Team ID, visible at the top of the page.
## authentik
20. Under _Resources -> Sources_ Click **Create Apple OAuth Source**
21. **Name**: `Apple`
22. **Slug**: `apple`
23. **Consumer Key:** The identifier from step 9, then `;`, then your Team ID from step 19, then `;`, then the Key ID from step 18.
Example: `io.goauthentik.dev-local;JQNH45HN7V;XFBNJ82BV6`
24. **Consumer Secret:** Paste the contents of the keyfile you've downloaded
Save, and you now have Apple as a source.
:::note
For more details on how-to have the new source display on the Login Page see the Sources page.
:::

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

View File

@ -33,7 +33,7 @@ Here is an example of a completed OAuth2 screen for Discord.
![Example Screen](discord4.png) ![Example Screen](discord4.png)
## Authentik ## authentik
8. Under _Resources -> Sources_ Click **Create Discord OAuth Source** 8. Under _Resources -> Sources_ Click **Create Discord OAuth Source**
@ -43,7 +43,7 @@ Here is an example of a completed OAuth2 screen for Discord.
12. **Consumer Secret:** Client Secret from step 5 12. **Consumer Secret:** Client Secret from step 5
13. **Provider type:** Discord 13. **Provider type:** Discord
Here is an exmple of a complete Authentik Discord OAuth Source Here is an example of a complete authentik Discord OAuth Source
![Example Screen](discord5.png) ![Example Screen](discord5.png)

View File

@ -17,7 +17,7 @@ The following placeholders will be used:
![Register OAuth App](githubdeveloper1.png) ![Register OAuth App](githubdeveloper1.png)
2. **Application Name:** Choose a name users will recognize ie: Authentik 2. **Application Name:** Choose a name users will recognize ie: authentik
3. **Homepage URL**:: www.my.company 3. **Homepage URL**:: www.my.company
4. **Authorization callback URL**: https://authentik.company/source/oauth/callback/github 4. **Authorization callback URL**: https://authentik.company/source/oauth/callback/github
5. Click **Register Application** 5. Click **Register Application**
@ -29,7 +29,7 @@ Example screenshot
6. Copy the **Client ID** and _save it for later_ 6. Copy the **Client ID** and _save it for later_
7. Click **Generate a new client secret** and _save it for later_ You will not be able to see the secret again, so be sure to copy it now. 7. Click **Generate a new client secret** and _save it for later_ You will not be able to see the secret again, so be sure to copy it now.
## Authentik ## authentik
8. Under _Resources -> Sources_ Click **Create Github OAuth Source** 8. Under _Resources -> Sources_ Click **Create Github OAuth Source**
@ -49,7 +49,7 @@ As of June 20 2021 these URLS are correct. Here is the Github reference URL http
15. **Access token URL:** `https://github.com/login/oauth/access_token` 15. **Access token URL:** `https://github.com/login/oauth/access_token`
16. **Profile URL:** `https://api.github.com/user` 16. **Profile URL:** `https://api.github.com/user`
Here is an exmple of a complete Authentik Github OAuth Source Here is an example of a complete authentik Github OAuth Source
![Example Screen](githubexample2.png) ![Example Screen](githubexample2.png)

View File

@ -62,7 +62,7 @@ _I'm only going to list the mandatory/important fields to complete._
24. Click **Create** 24. Click **Create**
25. Copy and store _Your Client ID_ and _Your Client Secret_ for later 25. Copy and store _Your Client ID_ and _Your Client Secret_ for later
## Authentik ## authentik
26. Under _Resources -> Sources_ Click **Create Google OAuth Source** 26. Under _Resources -> Sources_ Click **Create Google OAuth Source**
@ -72,7 +72,7 @@ _I'm only going to list the mandatory/important fields to complete._
30. **Consumer Secret:** Your Client Secret from step 25 30. **Consumer Secret:** Your Client Secret from step 25
31. **Provider Type:** Google 31. **Provider Type:** Google
Here is an exmple of a complete Authentik Google OAuth Source Here is an example of a complete authentik Google OAuth Source
![Example Screen](authentiksource.png) ![Example Screen](authentiksource.png)

View File

@ -8,7 +8,7 @@ Allows users to authenticate using their Plex credentials
None None
## Authentik -> Sources ## authentik -> Sources
Add _Plex_ as a _source_ Add _Plex_ as a _source_

View File

@ -73,7 +73,7 @@ server {
# authentik-specific config # authentik-specific config
auth_request /akprox/auth/nginx; auth_request /akprox/auth/nginx;
error_page 401 = @akprox_signin; error_page 401 = @akprox_signin;
# For domain level, use the below error_page to redirect to your Authentik server with the full redirect path # For domain level, use the below error_page to redirect to your authentik server with the full redirect path
# error_page 401 =302 https://authentik.company/akprox/start?rd=$scheme://$http_host$request_uri; # error_page 401 =302 https://authentik.company/akprox/start?rd=$scheme://$http_host$request_uri;
# translate headers from the outposts back to the actual upstream # translate headers from the outposts back to the actual upstream

View File

@ -69,7 +69,7 @@ error_reporting:
### Upgrading ### Upgrading
This upgrade only applies if you are upgrading from a running 0.9 instance. Authentik detects this on startup, and automatically executes this upgrade. This upgrade only applies if you are upgrading from a running 0.9 instance. authentik detects this on startup, and automatically executes this upgrade.
Because this upgrade brings the new OAuth2 Provider, the old providers will be lost in the process. Make sure to take note of the providers you want to bring over. Because this upgrade brings the new OAuth2 Provider, the old providers will be lost in the process. Make sure to take note of the providers you want to bring over.

View File

@ -64,6 +64,7 @@ module.exports = {
label: "as Source", label: "as Source",
items: [ items: [
"integrations/sources/index", "integrations/sources/index",
"integrations/sources/apple/index",
"integrations/sources/active-directory/index", "integrations/sources/active-directory/index",
"integrations/sources/discord/index", "integrations/sources/discord/index",
"integrations/sources/github/index", "integrations/sources/github/index",