From 6821679fbc7e24cdeecf93fed8bf2b7e1a324234 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Tue, 13 Apr 2021 19:57:33 +0200 Subject: [PATCH] *: add support for bearer authentication on API Signed-off-by: Jens Langhammer --- authentik/api/auth.py | 40 +++++++++++++++----------------- authentik/api/tests/test_auth.py | 9 ++++++- authentik/root/settings.py | 2 +- outpost/pkg/ak/api_ws.go | 3 +-- swagger.yaml | 4 ++-- web/src/api/Config.ts | 1 - 6 files changed, 31 insertions(+), 28 deletions(-) diff --git a/authentik/api/auth.py b/authentik/api/auth.py index 52f59ab5b..6bcd511f7 100644 --- a/authentik/api/auth.py +++ b/authentik/api/auth.py @@ -10,29 +10,29 @@ from structlog.stdlib import get_logger from authentik.core.models import Token, TokenIntents, User LOGGER = get_logger() -X_AUTHENTIK_PREVENT_BASIC_HEADER = "HTTP_X_AUTHENTIK_PREVENT_BASIC" def token_from_header(raw_header: bytes) -> Optional[Token]: """raw_header in the Format of `Basic dGVzdDp0ZXN0`""" auth_credentials = raw_header.decode() # Accept headers with Type format and without - if " " in auth_credentials: - auth_type, auth_credentials = auth_credentials.split() - if auth_type.lower() != "basic": - LOGGER.debug( - "Unsupported authentication type, denying", type=auth_type.lower() - ) - return None - try: - auth_credentials = b64decode(auth_credentials.encode()).decode() - except (UnicodeDecodeError, Error): + if " " not in auth_credentials: return None - # Accept credentials with username and without - if ":" in auth_credentials: - _, password = auth_credentials.split(":") - else: - password = auth_credentials + auth_type, auth_credentials = auth_credentials.split() + if auth_type.lower() not in ["basic", "bearer"]: + LOGGER.debug("Unsupported authentication type, denying", type=auth_type.lower()) + return None + password = auth_credentials + if auth_type.lower() == "basic": + try: + auth_credentials = b64decode(auth_credentials.encode()).decode() + except (UnicodeDecodeError, Error): + return None + # Accept credentials with username and without + if ":" in auth_credentials: + _, password = auth_credentials.split(":") + else: + password = auth_credentials if password == "": # nosec return None tokens = Token.filter_not_expired(key=password, intent=TokenIntents.INTENT_API) @@ -43,10 +43,10 @@ def token_from_header(raw_header: bytes) -> Optional[Token]: class AuthentikTokenAuthentication(BaseAuthentication): - """Token-based authentication using HTTP Basic authentication""" + """Token-based authentication using HTTP Bearer authentication""" def authenticate(self, request: Request) -> Union[tuple[User, Any], None]: - """Token-based authentication using HTTP Basic authentication""" + """Token-based authentication using HTTP Bearer authentication""" auth = get_authorization_header(request) token = token_from_header(auth) @@ -56,6 +56,4 @@ class AuthentikTokenAuthentication(BaseAuthentication): return (token.user, None) def authenticate_header(self, request: Request) -> str: - if X_AUTHENTIK_PREVENT_BASIC_HEADER in request._request.META: - return "" - return 'Basic realm="authentik"' + return "Bearer" diff --git a/authentik/api/tests/test_auth.py b/authentik/api/tests/test_auth.py index 262186484..558bf603c 100644 --- a/authentik/api/tests/test_auth.py +++ b/authentik/api/tests/test_auth.py @@ -11,7 +11,7 @@ from authentik.core.models import Token, TokenIntents class TestAPIAuth(TestCase): """Test API Authentication""" - def test_valid(self): + def test_valid_basic(self): """Test valid token""" token = Token.objects.create( intent=TokenIntents.INTENT_API, user=get_anonymous_user() @@ -19,6 +19,13 @@ class TestAPIAuth(TestCase): auth = b64encode(f":{token.key}".encode()).decode() self.assertEqual(token_from_header(f"Basic {auth}".encode()), token) + def test_valid_bearer(self): + """Test valid token""" + token = Token.objects.create( + intent=TokenIntents.INTENT_API, user=get_anonymous_user() + ) + self.assertEqual(token_from_header(f"Bearer {token.key}".encode()), token) + def test_invalid_type(self): """Test invalid type""" self.assertIsNone(token_from_header("foo bar".encode())) diff --git a/authentik/root/settings.py b/authentik/root/settings.py index 49025ba40..ec052c734 100644 --- a/authentik/root/settings.py +++ b/authentik/root/settings.py @@ -143,7 +143,7 @@ SWAGGER_SETTINGS = { "authentik.api.pagination_schema.PaginationInspector", ], "SECURITY_DEFINITIONS": { - "token": {"type": "apiKey", "name": "Authorization", "in": "header"} + "Bearer": {"type": "apiKey", "name": "Authorization", "in": "header"} }, } diff --git a/outpost/pkg/ak/api_ws.go b/outpost/pkg/ak/api_ws.go index c2fe4dc31..28a74b17a 100644 --- a/outpost/pkg/ak/api_ws.go +++ b/outpost/pkg/ak/api_ws.go @@ -2,7 +2,6 @@ package ak import ( "crypto/tls" - "encoding/base64" "fmt" "net/http" "net/url" @@ -20,7 +19,7 @@ func (ac *APIController) initWS(pbURL url.URL, outpostUUID strfmt.UUID) { pathTemplate := "%s://%s/ws/outpost/%s/" scheme := strings.ReplaceAll(pbURL.Scheme, "http", "ws") - authHeader := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("Basic :%s", ac.token))) + authHeader := fmt.Sprintf("Bearer %s", ac.token) header := http.Header{ "Authorization": []string{authHeader}, diff --git a/swagger.yaml b/swagger.yaml index 3563754e4..23424d8cd 100755 --- a/swagger.yaml +++ b/swagger.yaml @@ -13,12 +13,12 @@ consumes: produces: - application/json securityDefinitions: - token: + Bearer: type: apiKey name: Authorization in: header security: - - token: [] + - Bearer: [] paths: /admin/apps/: get: diff --git a/web/src/api/Config.ts b/web/src/api/Config.ts index 261279226..7a47ed0d4 100644 --- a/web/src/api/Config.ts +++ b/web/src/api/Config.ts @@ -16,7 +16,6 @@ export const DEFAULT_CONFIG = new Configuration({ basePath: "/api/v2beta", headers: { "X-CSRFToken": getCookie("authentik_csrf"), - "X-Authentik-Prevent-Basic": "true" }, middleware: [ API_DRAWER_MIDDLEWARE,