Skip to content

Device code flow does not work with client id sent via HTTP Basic Auth #20454

@brooklynbagel

Description

@brooklynbagel

Describe the bug

Some clients may try to sent the client id for device code flow via Authentication header instead of the POST body:

POST /application/o/device/ HTTP/1.1
Host: authentik.company
Authorization: Basic <encoded>

where <encoded> is a placeholder for the base64 encoded string of client_id: with no password.

When trying to use device code flow with such a client instead of the expected response described here I am getting a "Client authentication failed (e.g., unknown client, no client authentication included, or unsupported authentication method)" error.

The below example sending the client id in the POST body works, but the subsequent example does not:

$ curl -v -X POST http://localhost:9000/application/o/device/ -d "client_id=xmhYexEdD3F1idgo9jtZj0Ys4QUbmbjeYFlxkZ8I"
Note: Unnecessary use of -X or --request, POST is already inferred.
* Host localhost:9000 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
*   Trying [::1]:9000...
* Connected to localhost (::1) port 9000
> POST /application/o/device/ HTTP/1.1
> Host: localhost:9000
> User-Agent: curl/8.7.1
> Accept: */*
> Content-Length: 50
> Content-Type: application/x-www-form-urlencoded
>
* upload completely sent off: 50 bytes
< HTTP/1.1 200 OK
...
* Connection #0 to host localhost left intact
{"device_code": "NV/Nri\\}/Xau-|Kd<RSPT?R8KqLM5;_;MeY[#,MmO!AOg)T:!]9\"~GBJW~Gy5=d4'>H,]*$)Y:B,+s-Df(F9.q~b*j8.C+@*@:vc5k~&fmZaLn]Br\":&9hO`YLa.7R_M", "verification_uri": "http://localhost:9000/device", "verification_uri_complete": "http://localhost:9000/device?code=889650075", "user_code": "889650075", "expires_in": 60, "interval": 5

How to reproduce

  1. Create and apply device code flow, https://docs.goauthentik.io/add-secure-apps/providers/oauth2/device_code/#create-and-apply-a-device-code-flow
  2. Create an OAuth2 provider
  3. Copy client id
  4. Run curl -v -X POST https://<domain-for-authentik>/application/o/device/ -u "<client-id>:"
  5. Get response {"error": "invalid_client", "error_description": "Client authentication failed (e.g., unknown client, no client authentication included, or unsupported authentication method)", "request_id": "<request-id>"}

Example:

$ curl -v -X POST http://localhost:9000/application/o/device/ -u "xmhYexEdD3F1idgo9jtZj0Ys4QUbmbjeYFlxkZ8I:"
* Host localhost:9000 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
*   Trying [::1]:9000...
* Connected to localhost (::1) port 9000
* Server auth using Basic with user 'xmhYexEdD3F1idgo9jtZj0Ys4QUbmbjeYFlxkZ8I'
> POST /application/o/device/ HTTP/1.1
> Host: localhost:9000
> Authorization: Basic eG1oWWV4RWREM0YxaWRnbzlqdFpqMFlzNFFVYm1iamVZRmx4a1o4STo=
> User-Agent: curl/8.7.1
> Accept: */*
>
* Request completely sent off
< HTTP/1.1 400 Bad Request
...
* Connection #0 to host localhost left intact
{"error": "invalid_client", "error_description": "Client authentication failed (e.g., unknown client, no client authentication included, or unsupported authentication method)", "request_id": "bcd3973fcb3f4f5c9a17aa90c60d4211"}

Expected behavior

Should get a successful response (HTTP 200) with body as described in https://docs.goauthentik.io/add-secure-apps/providers/oauth2/device_code/#device-flow-initiation.

Example:

$ curl -v -X POST http://localhost:9000/application/o/device/ -u "xmhYexEdD3F1idgo9jtZj0Ys4QUbmbjeYFlxkZ8I:"
* Host localhost:9000 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
*   Trying [::1]:9000...
* Connected to localhost (::1) port 9000
* Server auth using Basic with user 'xmhYexEdD3F1idgo9jtZj0Ys4QUbmbjeYFlxkZ8I'
> POST /application/o/device/ HTTP/1.1
> Host: localhost:9000
> Authorization: Basic eG1oWWV4RWREM0YxaWRnbzlqdFpqMFlzNFFVYm1iamVZRmx4a1o4STo=
> User-Agent: curl/8.7.1
> Accept: */*
>
< HTTP/1.1 200 OK
...
* Connection #0 to host localhost left intact
{"device_code": "NV/Nri\\}/Xau-|Kd<RSPT?R8KqLM5;_;MeY[#,MmO!AOg)T:!]9\"~GBJW~Gy5=d4'>H,]*$)Y:B,+s-Df(F9.q~b*j8.C+@*@:vc5k~&fmZaLn]Br\":&9hO`YLa.7R_M", "verification_uri": "http://localhost:9000/device", "verification_uri_complete": "http://localhost:9000/device?code=889650075", "user_code": "889650075", "expires_in": 60, "interval": 5

Screenshots

No response

Additional context

Per RFC 8628

The client authentication requirements of Section 3.2.1 of [RFC6749]
apply to requests on this endpoint, which means that confidential
clients (those that have established client credentials) authenticate
in the same manner as when making requests to the token endpoint, and
public clients provide the "client_id" parameter to identify
themselves.

and Authentik already supports HTTP Basic Authentication elsewhere:

def extract_client_auth(request: HttpRequest) -> tuple[str, str]:
"""
Get client credentials using HTTP Basic Authentication method.
Or try getting parameters via POST.
See: http://tools.ietf.org/html/rfc6750#section-2.1
Return a tuple `(client_id, client_secret)`.
"""
auth_header = request.META.get("HTTP_AUTHORIZATION", "")
if re.compile(r"^Basic\s{1}.+$").match(auth_header):
b64_user_pass = auth_header.split()[1]
try:
user_pass = b64decode(b64_user_pass).decode("utf-8").partition(":")
client_id, _, client_secret = user_pass
except ValueError, Error:
client_id = client_secret = "" # nosec
else:
client_id = request.POST.get("client_id", "")
client_secret = request.POST.get("client_secret", "")
return (client_id, client_secret)

Deployment Method

Docker

Version

2025.12.4

Relevant log output

From docker logs when making request

server-1      | {"auth_via": "unauthenticated", "domain_url": "localhost", "event": "/application/o/device/", "host": "localhost:9000", "level": "info", "logger": "authentik.asgi", "method": "POST", "pid": 169, "remote": "192.168.148.1", "request_id": "9db5ee0eeed74c60bb9b97779cbab9b7", "runtime": 16, "schema_name": "public", "scheme": "http", "status": 400, "timestamp": "2026-02-21T17:54:04.908386", "user": "", "user_agent": "curl/8.7.1"}

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingtriageAdd this label to issues that need to be triaged

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions