*: add support for bearer authentication on API
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
parent
513d3c1c31
commit
6821679fbc
|
@ -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"
|
||||
|
|
|
@ -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()))
|
||||
|
|
|
@ -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"}
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -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},
|
||||
|
|
|
@ -13,12 +13,12 @@ consumes:
|
|||
produces:
|
||||
- application/json
|
||||
securityDefinitions:
|
||||
token:
|
||||
Bearer:
|
||||
type: apiKey
|
||||
name: Authorization
|
||||
in: header
|
||||
security:
|
||||
- token: []
|
||||
- Bearer: []
|
||||
paths:
|
||||
/admin/apps/:
|
||||
get:
|
||||
|
|
|
@ -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,
|
||||
|
|
Reference in a new issue