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()
AUTHENTIK_SOURCES_OAUTH_TYPES = [
"authentik.sources.oauth.types.apple",
"authentik.sources.oauth.types.azure_ad",
"authentik.sources.oauth.types.discord",
"authentik.sources.oauth.types.facebook",
"authentik.sources.oauth.types.github",
"authentik.sources.oauth.types.google",
"authentik.sources.oauth.types.oidc",
"authentik.sources.oauth.types.reddit",
"authentik.sources.oauth.types.twitter",
"authentik.sources.oauth.types.azure_ad",
"authentik.sources.oauth.types.oidc",
]

View File

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

View File

@ -20,10 +20,16 @@ class OAuth2Client(BaseOAuthClient):
"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:
"Check optional state parameter."
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
if stored is not None:
if returned is not None:
@ -38,23 +44,31 @@ class OAuth2Client(BaseOAuthClient):
"Generate state optional parameter."
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]]:
"Fetch access token from callback request."
callback = self.request.build_absolute_uri(self.callback or self.request.path)
if not self.check_application_state():
LOGGER.warning("Application state check failed.")
return None
if "code" in self.request.GET:
args = {
"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:
code = self.get_request_arg("code", None)
if not code:
LOGGER.warning("No code returned by the source")
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:
access_token_url = self.source.type.access_token_url or ""
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]:
"Get request parameters for redirect url."
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] = {
"client_id": client_id,
"redirect_uri": callback,

View File

@ -2,7 +2,6 @@
from typing import TYPE_CHECKING, Optional, Type
from django.db import models
from django.templatetags.static import static
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from rest_framework.serializers import Serializer
@ -49,7 +48,7 @@ class OAuthSource(Source):
consumer_secret = models.TextField()
@property
def type(self) -> "SourceType":
def type(self) -> Type["SourceType"]:
"""Return the provider instance for this source"""
from authentik.sources.oauth.types.manager import MANAGER
@ -67,6 +66,7 @@ class OAuthSource(Source):
@property
def ui_login_button(self) -> UILoginButton:
provider_type = self.type
return UILoginButton(
challenge=RedirectChallenge(
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,
)
@ -173,6 +173,16 @@ class OpenIDConnectOAuthSource(OAuthSource):
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):
"""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"""
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 authentik.sources.oauth.views.callback import OAuthCallback
@ -22,8 +23,8 @@ class SourceType:
callback_view = OAuthCallback
redirect_view = OAuthRedirect
name: str
slug: str
name: str = "default"
slug: str = "default"
urls_customizable = False
@ -32,12 +33,16 @@ class SourceType:
access_token_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:
"""Manager to hold all Source types."""
def __init__(self) -> None:
self.__sources: list[SourceType] = []
self.__sources: list[Type[SourceType]] = []
def type(self):
"""Class decorator to register classes inline."""
@ -56,14 +61,14 @@ class SourceTypeManager:
"""Get list of tuples of all registered names"""
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"""
found_type = None
for src_type in self.__sources:
if src_type.slug == type_name:
return src_type
if not found_type:
found_type = SourceType()
found_type = SourceType
LOGGER.warning(
"no matching type found, using default",
wanted=type_name,

View File

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

View File

@ -1,6 +1,8 @@
"""Dispatch OAuth views to respective views"""
from django.shortcuts import get_object_or_404
from django.utils.decorators import method_decorator
from django.views import View
from django.views.decorators.csrf import csrf_exempt
from structlog.stdlib import get_logger
from authentik.sources.oauth.models import OAuthSource
@ -9,6 +11,7 @@ from authentik.sources.oauth.types.manager import MANAGER, RequestKind
LOGGER = get_logger()
@method_decorator(csrf_exempt, name="dispatch")
class DispatcherView(View):
"""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/",
"type": ChallengeTypes.REDIRECT.value,
},
"icon_url": "/static/authentik/sources/.svg",
"icon_url": "/static/authentik/sources/default.svg",
"name": "test",
}
],
@ -170,7 +170,7 @@ class TestIdentificationStage(APITestCase):
"to": "/source/oauth/login/test/",
"type": ChallengeTypes.REDIRECT.value,
},
"icon_url": "/static/authentik/sources/.svg",
"icon_url": "/static/authentik/sources/default.svg",
"name": "test",
}
],
@ -226,7 +226,7 @@ class TestIdentificationStage(APITestCase):
},
"sources": [
{
"icon_url": "/static/authentik/sources/.svg",
"icon_url": "/static/authentik/sources/default.svg",
"name": "test",
"challenge": {
"component": "xak-flow-redirect",
@ -280,7 +280,7 @@ class TestIdentificationStage(APITestCase):
"to": "/source/oauth/login/test/",
"type": ChallengeTypes.REDIRECT.value,
},
"icon_url": "/static/authentik/sources/.svg",
"icon_url": "/static/authentik/sources/default.svg",
"name": "test",
}
],

View File

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

View File

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

View File

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

View File

@ -50,7 +50,7 @@ In authentik, create an application which uses this provider. Optionally apply a
### Step 3
Obtain your Metadata URL from Authentik.
Obtain your Metadata URL from authentik.
1. Click on the BookStack Provider
2. Click the Metadata Tab
@ -69,7 +69,7 @@ Modify the following Example SAML config and paste incorporate into your `.env`
AUTH_METHOD=saml2
# Set the display name to be shown on the login button.
# (Login with <name>)
SAML2_NAME=Authentik
SAML2_NAME=authentik
# Name of the attribute which provides the user's email address
SAML2_EMAIL_ATTRIBUTE=email
# 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.
- `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:
@ -57,7 +57,7 @@ Portainer by default shows commas between each item in the Scopes field. Do **N
![](./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.

View File

@ -76,9 +76,9 @@ auth:
# The auth url to send users to if they want to authenticate using OpenID Connect.
authurl: https://authentik.company/application/o/vikunja/
# 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.
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

View File

@ -39,7 +39,7 @@ import TabItem from '@theme/TabItem';
{label: 'Standalone', value: 'standalone'},
]}>
<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
environment:
@ -58,11 +58,11 @@ environment:
```
</TabItem>
<TabItem value="standalone">
edit `.env` and add the following:
```ini
# Authentik OAUTH Config
# authentik OAUTH Config
OAUTH2_ENABLED='true'
OAUTH2_LOGIN_STYLE='redirect'
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.
- `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:
@ -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_
:::
### 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.

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)
## Authentik
## authentik
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
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)
@ -51,4 +51,4 @@ Save, and you now have Discord as a source.
:::note
For more details on how-to have the new source display on the Login Page see the Sources page
:::
:::

View File

@ -17,7 +17,7 @@ The following placeholders will be used:
![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
4. **Authorization callback URL**: https://authentik.company/source/oauth/callback/github
5. Click **Register Application**
@ -27,9 +27,9 @@ Example screenshot
![Example Screen](githubdeveloperexample.png)
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**
@ -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`
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)

View File

@ -15,7 +15,7 @@ The following placeholders will be used:
You will need to create a new project, and OAuth credentials in the Google Developer console. The developer console can be overwhelming at first.
1. Visit https://console.developers.google.com/ to create a new project
2. Create a New project.
2. Create a New project.
![Example Screen](googledeveloper1.png)
@ -62,7 +62,7 @@ _I'm only going to list the mandatory/important fields to complete._
24. Click **Create**
25. Copy and store _Your Client ID_ and _Your Client Secret_ for later
## Authentik
## authentik
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
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)
@ -80,4 +80,4 @@ Save, and you now have Google as a source.
:::note
For more details on how-to have the new source display on the Login Page see the Sources page
:::
:::

View File

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

View File

@ -73,7 +73,7 @@ server {
# authentik-specific config
auth_request /akprox/auth/nginx;
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;
# translate headers from the outposts back to the actual upstream

View File

@ -69,7 +69,7 @@ error_reporting:
### 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.

View File

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