diff --git a/mozilla_django_oidc_db/admin.py b/mozilla_django_oidc_db/admin.py
index eb57aba..4c0009a 100644
--- a/mozilla_django_oidc_db/admin.py
+++ b/mozilla_django_oidc_db/admin.py
@@ -35,6 +35,7 @@ class OpenIDConnectConfigAdmin(SingletonModelAdmin):
"oidc_op_jwks_endpoint",
"oidc_op_authorization_endpoint",
"oidc_op_token_endpoint",
+ "oidc_token_use_basic_auth",
"oidc_op_user_endpoint",
)
},
diff --git a/mozilla_django_oidc_db/migrations/0015_openidconnectconfig_oidc_token_use_basic_auth.py b/mozilla_django_oidc_db/migrations/0015_openidconnectconfig_oidc_token_use_basic_auth.py
new file mode 100644
index 0000000..872bd1a
--- /dev/null
+++ b/mozilla_django_oidc_db/migrations/0015_openidconnectconfig_oidc_token_use_basic_auth.py
@@ -0,0 +1,22 @@
+# Generated by Django 3.2.23 on 2024-02-05 16:51
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("mozilla_django_oidc_db", "0014_alter_openidconnectconfig_groups_claim"),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="openidconnectconfig",
+ name="oidc_token_use_basic_auth",
+ field=models.BooleanField(
+ default=False,
+ help_text="If enabled, the client ID and secret are sent in the HTTP Basic auth header when obtaining the access token. Otherwise, they are sent in the request body.",
+ verbose_name="Use Basic auth for token endpoint",
+ ),
+ ),
+ ]
diff --git a/mozilla_django_oidc_db/models.py b/mozilla_django_oidc_db/models.py
index c404e83..7a672d8 100644
--- a/mozilla_django_oidc_db/models.py
+++ b/mozilla_django_oidc_db/models.py
@@ -161,6 +161,15 @@ class OpenIDConnectConfigBase(SingletonModel):
max_length=1000,
help_text=_("URL of your OpenID Connect provider token endpoint"),
)
+ oidc_token_use_basic_auth = models.BooleanField(
+ _("Use Basic auth for token endpoint"),
+ default=False,
+ help_text=_(
+ "If enabled, the client ID and secret are sent in the HTTP Basic auth "
+ "header when obtaining the access token. Otherwise, they are sent in the "
+ "request body.",
+ ),
+ )
oidc_op_user_endpoint = models.URLField(
_("User endpoint"),
max_length=1000,
diff --git a/tests/cassettes/test_integration_oidc_flow_variants/test_credentials_in_basic_auth_header.yaml b/tests/cassettes/test_integration_oidc_flow_variants/test_credentials_in_basic_auth_header.yaml
new file mode 100644
index 0000000..6f4d143
--- /dev/null
+++ b/tests/cassettes/test_integration_oidc_flow_variants/test_credentials_in_basic_auth_header.yaml
@@ -0,0 +1,319 @@
+interactions:
+- request:
+ body: null
+ headers:
+ Accept:
+ - '*/*'
+ Accept-Encoding:
+ - gzip, deflate
+ Connection:
+ - keep-alive
+ User-Agent:
+ - python-requests/2.31.0
+ method: GET
+ uri: http://localhost:8080/realms/test/.well-known/openid-configuration
+ response:
+ body:
+ string: '{"issuer":"http://localhost:8080/realms/test","authorization_endpoint":"http://localhost:8080/realms/test/protocol/openid-connect/auth","token_endpoint":"http://localhost:8080/realms/test/protocol/openid-connect/token","introspection_endpoint":"http://localhost:8080/realms/test/protocol/openid-connect/token/introspect","userinfo_endpoint":"http://localhost:8080/realms/test/protocol/openid-connect/userinfo","end_session_endpoint":"http://localhost:8080/realms/test/protocol/openid-connect/logout","frontchannel_logout_session_supported":true,"frontchannel_logout_supported":true,"jwks_uri":"http://localhost:8080/realms/test/protocol/openid-connect/certs","check_session_iframe":"http://localhost:8080/realms/test/protocol/openid-connect/login-status-iframe.html","grant_types_supported":["authorization_code","implicit","refresh_token","password","client_credentials","urn:openid:params:grant-type:ciba","urn:ietf:params:oauth:grant-type:device_code"],"acr_values_supported":["0","1"],"response_types_supported":["code","none","id_token","token","id_token
+ token","code id_token","code token","code id_token token"],"subject_types_supported":["public","pairwise"],"id_token_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512"],"id_token_encryption_alg_values_supported":["RSA-OAEP","RSA-OAEP-256","RSA1_5"],"id_token_encryption_enc_values_supported":["A256GCM","A192GCM","A128GCM","A128CBC-HS256","A192CBC-HS384","A256CBC-HS512"],"userinfo_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512","none"],"userinfo_encryption_alg_values_supported":["RSA-OAEP","RSA-OAEP-256","RSA1_5"],"userinfo_encryption_enc_values_supported":["A256GCM","A192GCM","A128GCM","A128CBC-HS256","A192CBC-HS384","A256CBC-HS512"],"request_object_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512","none"],"request_object_encryption_alg_values_supported":["RSA-OAEP","RSA-OAEP-256","RSA1_5"],"request_object_encryption_enc_values_supported":["A256GCM","A192GCM","A128GCM","A128CBC-HS256","A192CBC-HS384","A256CBC-HS512"],"response_modes_supported":["query","fragment","form_post","query.jwt","fragment.jwt","form_post.jwt","jwt"],"registration_endpoint":"http://localhost:8080/realms/test/clients-registrations/openid-connect","token_endpoint_auth_methods_supported":["private_key_jwt","client_secret_basic","client_secret_post","tls_client_auth","client_secret_jwt"],"token_endpoint_auth_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512"],"introspection_endpoint_auth_methods_supported":["private_key_jwt","client_secret_basic","client_secret_post","tls_client_auth","client_secret_jwt"],"introspection_endpoint_auth_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512"],"authorization_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512"],"authorization_encryption_alg_values_supported":["RSA-OAEP","RSA-OAEP-256","RSA1_5"],"authorization_encryption_enc_values_supported":["A256GCM","A192GCM","A128GCM","A128CBC-HS256","A192CBC-HS384","A256CBC-HS512"],"claims_supported":["aud","sub","iss","auth_time","name","given_name","family_name","preferred_username","email","acr"],"claim_types_supported":["normal"],"claims_parameter_supported":true,"scopes_supported":["openid","email","roles","phone","profile","address","kvk","web-origins","microprofile-jwt","acr","offline_access","bsn"],"request_parameter_supported":true,"request_uri_parameter_supported":true,"require_request_uri_registration":true,"code_challenge_methods_supported":["plain","S256"],"tls_client_certificate_bound_access_tokens":true,"revocation_endpoint":"http://localhost:8080/realms/test/protocol/openid-connect/revoke","revocation_endpoint_auth_methods_supported":["private_key_jwt","client_secret_basic","client_secret_post","tls_client_auth","client_secret_jwt"],"revocation_endpoint_auth_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512"],"backchannel_logout_supported":true,"backchannel_logout_session_supported":true,"device_authorization_endpoint":"http://localhost:8080/realms/test/protocol/openid-connect/auth/device","backchannel_token_delivery_modes_supported":["poll","ping"],"backchannel_authentication_endpoint":"http://localhost:8080/realms/test/protocol/openid-connect/ext/ciba/auth","backchannel_authentication_request_signing_alg_values_supported":["PS384","ES384","RS384","ES256","RS256","ES512","PS256","PS512","RS512"],"require_pushed_authorization_requests":false,"pushed_authorization_request_endpoint":"http://localhost:8080/realms/test/protocol/openid-connect/ext/par/request","mtls_endpoint_aliases":{"token_endpoint":"http://localhost:8080/realms/test/protocol/openid-connect/token","revocation_endpoint":"http://localhost:8080/realms/test/protocol/openid-connect/revoke","introspection_endpoint":"http://localhost:8080/realms/test/protocol/openid-connect/token/introspect","device_authorization_endpoint":"http://localhost:8080/realms/test/protocol/openid-connect/auth/device","registration_endpoint":"http://localhost:8080/realms/test/clients-registrations/openid-connect","userinfo_endpoint":"http://localhost:8080/realms/test/protocol/openid-connect/userinfo","pushed_authorization_request_endpoint":"http://localhost:8080/realms/test/protocol/openid-connect/ext/par/request","backchannel_authentication_endpoint":"http://localhost:8080/realms/test/protocol/openid-connect/ext/ciba/auth"},"authorization_response_iss_parameter_supported":true}'
+ headers:
+ Cache-Control:
+ - no-cache, must-revalidate, no-transform, no-store
+ Content-Type:
+ - application/json;charset=UTF-8
+ Referrer-Policy:
+ - no-referrer
+ Strict-Transport-Security:
+ - max-age=31536000; includeSubDomains
+ X-Content-Type-Options:
+ - nosniff
+ X-Frame-Options:
+ - SAMEORIGIN
+ X-XSS-Protection:
+ - 1; mode=block
+ content-length:
+ - '5847'
+ status:
+ code: 200
+ message: OK
+- request:
+ body: null
+ headers:
+ Accept:
+ - '*/*'
+ Accept-Encoding:
+ - gzip, deflate
+ Connection:
+ - keep-alive
+ User-Agent:
+ - python-requests/2.31.0
+ method: GET
+ uri: http://localhost:8080/realms/test/protocol/openid-connect/auth?response_type=code&scope=openid+email+profile+bsn+kvk&client_id=testid&redirect_uri=http%3A%2F%2Ftestserver%2Foidc%2Fcallback%2F&state=not-a-random-string&nonce=not-a-random-string
+ response:
+ body:
+ string: "\n\n\n
\n \n
+ \ \n \n\n \n Sign
+ in to test\n \n \n \n \n \n \n \n\n\n\n\n
+ \ \n
\n
+ \ \n
\n
+ \
\n\n\n
\n \n\n\n\n\n\n
+ \
\n
\n\n
\n
\n\n\n"
+ headers:
+ Cache-Control:
+ - no-store, must-revalidate, max-age=0
+ Content-Language:
+ - en
+ Content-Security-Policy:
+ - frame-src 'self'; frame-ancestors 'self'; object-src 'none';
+ Content-Type:
+ - text/html;charset=utf-8
+ Referrer-Policy:
+ - no-referrer
+ Set-Cookie:
+ - AUTH_SESSION_ID=5d288792-2e5a-40ef-a0ad-b16c8fe0502d; Version=1; Path=/realms/test/;
+ SameSite=None; Secure; HttpOnly
+ - AUTH_SESSION_ID_LEGACY=5d288792-2e5a-40ef-a0ad-b16c8fe0502d; Version=1; Path=/realms/test/;
+ HttpOnly
+ - KC_RESTART=eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJlNzE1ZTA1MS02Y2RiLTQ4Y2MtYjRmNC1mMDcyMmM4MWY5ZDMifQ.eyJjaWQiOiJ0ZXN0aWQiLCJwdHkiOiJvcGVuaWQtY29ubmVjdCIsInJ1cmkiOiJodHRwOi8vdGVzdHNlcnZlci9vaWRjL2NhbGxiYWNrLyIsImFjdCI6IkFVVEhFTlRJQ0FURSIsIm5vdGVzIjp7InNjb3BlIjoib3BlbmlkIGVtYWlsIHByb2ZpbGUgYnNuIGt2ayIsImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9yZWFsbXMvdGVzdCIsInJlc3BvbnNlX3R5cGUiOiJjb2RlIiwicmVkaXJlY3RfdXJpIjoiaHR0cDovL3Rlc3RzZXJ2ZXIvb2lkYy9jYWxsYmFjay8iLCJzdGF0ZSI6Im5vdC1hLXJhbmRvbS1zdHJpbmciLCJub25jZSI6Im5vdC1hLXJhbmRvbS1zdHJpbmcifX0.i8j5h2oK7wCQJD0j4WgiObNnD6QLrcy1MjXZSIiFrD0;
+ Version=1; Path=/realms/test/; HttpOnly
+ Strict-Transport-Security:
+ - max-age=31536000; includeSubDomains
+ X-Content-Type-Options:
+ - nosniff
+ X-Frame-Options:
+ - SAMEORIGIN
+ X-Robots-Tag:
+ - none
+ X-XSS-Protection:
+ - 1; mode=block
+ content-length:
+ - '4474'
+ status:
+ code: 200
+ message: OK
+- request:
+ body: username=testuser&password=testuser&credentialId=&login=Sign+In
+ headers:
+ Accept:
+ - '*/*'
+ Accept-Encoding:
+ - gzip, deflate
+ Connection:
+ - keep-alive
+ Content-Length:
+ - '63'
+ Content-Type:
+ - application/x-www-form-urlencoded
+ Cookie:
+ - AUTH_SESSION_ID_LEGACY=5d288792-2e5a-40ef-a0ad-b16c8fe0502d; KC_RESTART=eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJlNzE1ZTA1MS02Y2RiLTQ4Y2MtYjRmNC1mMDcyMmM4MWY5ZDMifQ.eyJjaWQiOiJ0ZXN0aWQiLCJwdHkiOiJvcGVuaWQtY29ubmVjdCIsInJ1cmkiOiJodHRwOi8vdGVzdHNlcnZlci9vaWRjL2NhbGxiYWNrLyIsImFjdCI6IkFVVEhFTlRJQ0FURSIsIm5vdGVzIjp7InNjb3BlIjoib3BlbmlkIGVtYWlsIHByb2ZpbGUgYnNuIGt2ayIsImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9yZWFsbXMvdGVzdCIsInJlc3BvbnNlX3R5cGUiOiJjb2RlIiwicmVkaXJlY3RfdXJpIjoiaHR0cDovL3Rlc3RzZXJ2ZXIvb2lkYy9jYWxsYmFjay8iLCJzdGF0ZSI6Im5vdC1hLXJhbmRvbS1zdHJpbmciLCJub25jZSI6Im5vdC1hLXJhbmRvbS1zdHJpbmcifX0.i8j5h2oK7wCQJD0j4WgiObNnD6QLrcy1MjXZSIiFrD0
+ User-Agent:
+ - python-requests/2.31.0
+ method: POST
+ uri: http://localhost:8080/realms/test/login-actions/authenticate?session_code=Ox1pJ-7SUPl_PYPJtvTMpWGwIpTKXCi0LfdYB3UOGko&execution=5d476fe6-1c62-4f0a-9d3e-9e1f49df4766&client_id=testid&tab_id=CbUnOGyIbv4
+ response:
+ body:
+ string: ''
+ headers:
+ Cache-Control:
+ - no-store, must-revalidate, max-age=0
+ Content-Security-Policy:
+ - frame-src 'self'; frame-ancestors 'self'; object-src 'none';
+ Location:
+ - http://testserver/oidc/callback/?state=not-a-random-string&session_state=5d288792-2e5a-40ef-a0ad-b16c8fe0502d&iss=http%3A%2F%2Flocalhost%3A8080%2Frealms%2Ftest&code=a068e3fe-40c3-4d0d-8b16-871a80326589.5d288792-2e5a-40ef-a0ad-b16c8fe0502d.adf4ad83-4550-4619-9231-73bd8d700f45
+ Referrer-Policy:
+ - no-referrer
+ Set-Cookie:
+ - KEYCLOAK_LOCALE=; Version=1; Comment=Expiring cookie; Expires=Thu, 01-Jan-1970
+ 00:00:10 GMT; Max-Age=0; Path=/realms/test/; HttpOnly
+ - KC_RESTART=; Version=1; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Max-Age=0;
+ Path=/realms/test/; HttpOnly
+ - KC_AUTH_STATE=; Version=1; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Max-Age=0;
+ Path=/realms/test/
+ - KEYCLOAK_IDENTITY=eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJlNzE1ZTA1MS02Y2RiLTQ4Y2MtYjRmNC1mMDcyMmM4MWY5ZDMifQ.eyJleHAiOjE3MDcyNDc0NjUsImlhdCI6MTcwNzIxMTQ2NSwianRpIjoiNWRkZDMzYzMtMTVhNi00MmY5LTg3YWUtMTkxNDY4ZmViNDA0IiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL3JlYWxtcy90ZXN0Iiwic3ViIjoiYWExMGNmYzctMmM0ZC00MWY2LThmYWMtN2JmNDA1YzU3MmM0IiwidHlwIjoiU2VyaWFsaXplZC1JRCIsInNlc3Npb25fc3RhdGUiOiI1ZDI4ODc5Mi0yZTVhLTQwZWYtYTBhZC1iMTZjOGZlMDUwMmQiLCJzaWQiOiI1ZDI4ODc5Mi0yZTVhLTQwZWYtYTBhZC1iMTZjOGZlMDUwMmQiLCJzdGF0ZV9jaGVja2VyIjoiOWxCcmYwUWs0NUdYS0VwZjA3Nk1kdllZT08xZGVIQ0VCdGtQMk9VY2VXVSJ9.p1Rw-J6y-xMvBv9Ys6YfZDGYsLLGmT4sO15qCczwsbY;
+ Version=1; Path=/realms/test/; SameSite=None; Secure; HttpOnly
+ - KEYCLOAK_IDENTITY_LEGACY=eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJlNzE1ZTA1MS02Y2RiLTQ4Y2MtYjRmNC1mMDcyMmM4MWY5ZDMifQ.eyJleHAiOjE3MDcyNDc0NjUsImlhdCI6MTcwNzIxMTQ2NSwianRpIjoiNWRkZDMzYzMtMTVhNi00MmY5LTg3YWUtMTkxNDY4ZmViNDA0IiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL3JlYWxtcy90ZXN0Iiwic3ViIjoiYWExMGNmYzctMmM0ZC00MWY2LThmYWMtN2JmNDA1YzU3MmM0IiwidHlwIjoiU2VyaWFsaXplZC1JRCIsInNlc3Npb25fc3RhdGUiOiI1ZDI4ODc5Mi0yZTVhLTQwZWYtYTBhZC1iMTZjOGZlMDUwMmQiLCJzaWQiOiI1ZDI4ODc5Mi0yZTVhLTQwZWYtYTBhZC1iMTZjOGZlMDUwMmQiLCJzdGF0ZV9jaGVja2VyIjoiOWxCcmYwUWs0NUdYS0VwZjA3Nk1kdllZT08xZGVIQ0VCdGtQMk9VY2VXVSJ9.p1Rw-J6y-xMvBv9Ys6YfZDGYsLLGmT4sO15qCczwsbY;
+ Version=1; Path=/realms/test/; HttpOnly
+ - KEYCLOAK_SESSION=test/aa10cfc7-2c4d-41f6-8fac-7bf405c572c4/5d288792-2e5a-40ef-a0ad-b16c8fe0502d;
+ Version=1; Expires=Tue, 06-Feb-2024 19:24:25 GMT; Max-Age=36000; Path=/realms/test/;
+ SameSite=None; Secure
+ - KEYCLOAK_SESSION_LEGACY=test/aa10cfc7-2c4d-41f6-8fac-7bf405c572c4/5d288792-2e5a-40ef-a0ad-b16c8fe0502d;
+ Version=1; Expires=Tue, 06-Feb-2024 19:24:25 GMT; Max-Age=36000; Path=/realms/test/
+ - KEYCLOAK_REMEMBER_ME=; Version=1; Comment=Expiring cookie; Expires=Thu, 01-Jan-1970
+ 00:00:10 GMT; Max-Age=0; Path=/realms/test/; HttpOnly
+ Strict-Transport-Security:
+ - max-age=31536000; includeSubDomains
+ X-Content-Type-Options:
+ - nosniff
+ X-Frame-Options:
+ - SAMEORIGIN
+ X-Robots-Tag:
+ - none
+ X-XSS-Protection:
+ - 1; mode=block
+ content-length:
+ - '0'
+ status:
+ code: 302
+ message: Found
+- request:
+ body: client_id=testid&grant_type=authorization_code&code=a068e3fe-40c3-4d0d-8b16-871a80326589.5d288792-2e5a-40ef-a0ad-b16c8fe0502d.adf4ad83-4550-4619-9231-73bd8d700f45&redirect_uri=http%3A%2F%2Ftestserver%2Foidc%2Fcallback%2F
+ headers:
+ Accept:
+ - '*/*'
+ Accept-Encoding:
+ - gzip, deflate
+ Authorization:
+ - Basic dGVzdGlkOjdEQjNLVUFBaXpZQ2NtWnVmcEhSVk9jRDBUT2tOTzNJ
+ Connection:
+ - keep-alive
+ Content-Length:
+ - '220'
+ Content-Type:
+ - application/x-www-form-urlencoded
+ User-Agent:
+ - python-requests/2.31.0
+ method: POST
+ uri: http://localhost:8080/realms/test/protocol/openid-connect/token
+ response:
+ body:
+ string: '{"access_token":"eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI0VU5RQWN2VWN2LURGVU94XzRPMWd0MTNPZEpTb3RxRUtQWnVyczJ2UVc4In0.eyJleHAiOjE3MDcyMTE3NjUsImlhdCI6MTcwNzIxMTQ2NSwiYXV0aF90aW1lIjoxNzA3MjExNDY1LCJqdGkiOiIzN2E5MmJiNy1mZjViLTRkMGYtOTk2MC1kNDljOTU2YmMxMGYiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvcmVhbG1zL3Rlc3QiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiYWExMGNmYzctMmM0ZC00MWY2LThmYWMtN2JmNDA1YzU3MmM0IiwidHlwIjoiQmVhcmVyIiwiYXpwIjoidGVzdGlkIiwibm9uY2UiOiJub3QtYS1yYW5kb20tc3RyaW5nIiwic2Vzc2lvbl9zdGF0ZSI6IjVkMjg4NzkyLTJlNWEtNDBlZi1hMGFkLWIxNmM4ZmUwNTAyZCIsImFjciI6IjEiLCJhbGxvd2VkLW9yaWdpbnMiOlsiaHR0cDovLzEyNy4wLjAuMTo4MDAwIl0sInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJkZWZhdWx0LXJvbGVzLXRlc3QiLCJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJvcGVuaWQgZW1haWwgcHJvZmlsZSBrdmsgYnNuIiwic2lkIjoiNWQyODg3OTItMmU1YS00MGVmLWEwYWQtYjE2YzhmZTA1MDJkIiwia3ZrIjoiMDEyMzQ1Njc4IiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJ0ZXN0dXNlciIsImJzbiI6IjAwMDAwMDAwMCJ9.trACfjxXgErqoTdRhKSyrU08XeTZwYB_oEMn-GykJ6wBf1lUuuU5w9ahhWbA4xOdrFumZ-A732NaSg5FCAlPfXO5zvu8JlN4omlf_iL_ciTxEqhDM9p6wgGsrmxEiHO9JGWgVOXhViP_RinVHig1dniOLenvFhbPi6irhtFwPdymoCr9rnBmCHGyv6uUXlNqulrlLV3hnjoPr9BmySm1fe8_hDjXaXWCQPhZxcKC_1d6H9XixVgbV2MWQSyNnTAvpt87JxI-JZbtdzXOITXiysXLRDojFLx3NLoq4TY12C9zDjoTKQ8EE1T3PTF23YQ70lpj_m5Lr-ij3ICVx4M3Sg","expires_in":300,"refresh_expires_in":1799,"refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJlNzE1ZTA1MS02Y2RiLTQ4Y2MtYjRmNC1mMDcyMmM4MWY5ZDMifQ.eyJleHAiOjE3MDcyMTMyNjQsImlhdCI6MTcwNzIxMTQ2NSwianRpIjoiMGM3NDkyMWMtMWY0NS00NGEyLTk2ZDUtMzE4MDdiZWVkMmZkIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL3JlYWxtcy90ZXN0IiwiYXVkIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL3JlYWxtcy90ZXN0Iiwic3ViIjoiYWExMGNmYzctMmM0ZC00MWY2LThmYWMtN2JmNDA1YzU3MmM0IiwidHlwIjoiUmVmcmVzaCIsImF6cCI6InRlc3RpZCIsIm5vbmNlIjoibm90LWEtcmFuZG9tLXN0cmluZyIsInNlc3Npb25fc3RhdGUiOiI1ZDI4ODc5Mi0yZTVhLTQwZWYtYTBhZC1iMTZjOGZlMDUwMmQiLCJzY29wZSI6Im9wZW5pZCBlbWFpbCBwcm9maWxlIGt2ayBic24iLCJzaWQiOiI1ZDI4ODc5Mi0yZTVhLTQwZWYtYTBhZC1iMTZjOGZlMDUwMmQifQ.WzctyL39U8RvNEcyxu3rWoBm_ZIOoIzJbng6jGX40FE","token_type":"Bearer","id_token":"eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI0VU5RQWN2VWN2LURGVU94XzRPMWd0MTNPZEpTb3RxRUtQWnVyczJ2UVc4In0.eyJleHAiOjE3MDcyMTE3NjUsImlhdCI6MTcwNzIxMTQ2NSwiYXV0aF90aW1lIjoxNzA3MjExNDY1LCJqdGkiOiI2OWQ2NTk1ZC0xMWZiLTRhMDEtYjkzYi02N2RhZDAwZTZmMTciLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvcmVhbG1zL3Rlc3QiLCJhdWQiOiJ0ZXN0aWQiLCJzdWIiOiJhYTEwY2ZjNy0yYzRkLTQxZjYtOGZhYy03YmY0MDVjNTcyYzQiLCJ0eXAiOiJJRCIsImF6cCI6InRlc3RpZCIsIm5vbmNlIjoibm90LWEtcmFuZG9tLXN0cmluZyIsInNlc3Npb25fc3RhdGUiOiI1ZDI4ODc5Mi0yZTVhLTQwZWYtYTBhZC1iMTZjOGZlMDUwMmQiLCJhdF9oYXNoIjoiR0xSNGF0QVJUcjhISFNFcWx3MGZXUSIsImFjciI6IjEiLCJzaWQiOiI1ZDI4ODc5Mi0yZTVhLTQwZWYtYTBhZC1iMTZjOGZlMDUwMmQiLCJrdmsiOiIwMTIzNDU2NzgiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInByZWZlcnJlZF91c2VybmFtZSI6InRlc3R1c2VyIiwiYnNuIjoiMDAwMDAwMDAwIn0.Qh3SN-o-HJX_zMHQFY5B3cz-s_W8ymJkKbldxr3WkD1L7zG5eUxaqzFbCo0TQfsWfKNFQoFXLHzQY_3rkAECgJUE5hWLxbJyd9FQH06i1WqcM6qCWxGqq8EelrSHrSJ7kdBT3LGWriLAn_x64WqBmRSiKDdMAGSbQTZO2Ag8e0k3buAL3CR4Erch3cYFwfjrkRtGz5e78SfghnWbzhAK73gyP190-OWlHLG7n7fPT1CuiFw3Uuxl2t10sGadn1kNHOmbycbs8lzJhbGew5BD-dne9pLGN64uqwGc4k7fhTTxXGV9Fg6J6SFH23jvRp2EOnHkz6i-0N_FChjZppbcIQ","not-before-policy":0,"session_state":"5d288792-2e5a-40ef-a0ad-b16c8fe0502d","scope":"openid
+ email profile kvk bsn"}'
+ headers:
+ Cache-Control:
+ - no-store
+ Content-Type:
+ - application/json
+ Pragma:
+ - no-cache
+ Referrer-Policy:
+ - no-referrer
+ Strict-Transport-Security:
+ - max-age=31536000; includeSubDomains
+ X-Content-Type-Options:
+ - nosniff
+ X-Frame-Options:
+ - SAMEORIGIN
+ X-XSS-Protection:
+ - 1; mode=block
+ content-length:
+ - '3475'
+ status:
+ code: 200
+ message: OK
+- request:
+ body: null
+ headers:
+ Accept:
+ - '*/*'
+ Accept-Encoding:
+ - gzip, deflate
+ Connection:
+ - keep-alive
+ User-Agent:
+ - python-requests/2.31.0
+ method: GET
+ uri: http://localhost:8080/realms/test/protocol/openid-connect/certs
+ response:
+ body:
+ string: '{"keys":[{"kid":"4UNQAcvUcv-DFUOx_4O1gt13OdJSotqEKPZurs2vQW8","kty":"RSA","alg":"RS256","use":"sig","n":"2DOZ0qHie73SuFVR7civrl6r82YUiAghfzaMowjCg0o06AF--2lIS7vNV_PbsVVznPAAMqVrNG-8CcevEzvVZMQD9nH4DI7xlOxK0lrYu8rmMeSfOvXVbBVsWBZe0jnGNukZqjwmRE5__ttJdxPfIBT5-2L6mguQbDfhSUEEdIW7y7UfOXvqLqEcBtoIEB-ORKDTUIQwGZM5mSCy-cY3cHvvZfZVgaUUy5NvujPRXTMje4n_hG0KfEV-40G9qC2_Xvx4EooJzBZ6FSThiWhCpwhIvzcQqB6M9lHW7nU6wADhYPNCa2OKWvphwZ_zbrF4B9dmS6Zli5rBvbox9Hh45w","e":"AQAB","x5c":["MIIClzCCAX8CBgGNeYaMLTANBgkqhkiG9w0BAQsFADAPMQ0wCwYDVQQDDAR0ZXN0MB4XDTI0MDIwNTEzNDYxN1oXDTM0MDIwNTEzNDc1N1owDzENMAsGA1UEAwwEdGVzdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANgzmdKh4nu90rhVUe3Ir65eq/NmFIgIIX82jKMIwoNKNOgBfvtpSEu7zVfz27FVc5zwADKlazRvvAnHrxM71WTEA/Zx+AyO8ZTsStJa2LvK5jHknzr11WwVbFgWXtI5xjbpGao8JkROf/7bSXcT3yAU+fti+poLkGw34UlBBHSFu8u1Hzl76i6hHAbaCBAfjkSg01CEMBmTOZkgsvnGN3B772X2VYGlFMuTb7oz0V0zI3uJ/4RtCnxFfuNBvagtv178eBKKCcwWehUk4YloQqcISL83EKgejPZR1u51OsAA4WDzQmtjilr6YcGf826xeAfXZkumZYuawb26MfR4eOcCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAsnQG/Yi2g1XTCJn74hWv9MjxVAaZb4gBAc2AWm5VgAjhFEM9h6x6m1mQkq7JM4rIdAj8jw55Ok9CBVBIqq4G4cME3eUvVytkj2lC9zcRoAivjjZF2HPg7zNPa2TTR50asmHPRokppV6gewO/C+o5as+4P2zqDXBh61aRd/9kdQfkg14LBbH5/dYccAuvUqlTYC4IEPCvVmBNC1xsMjf0vohvoSjm9vL2bfqG/RJH0ScdCjOd5d2zju4/e2oVdluWm+vzKBQplc7tVMuKpn6LcLmVHiGNAl+EBIZH+WVLlTx0D1+kbHZsfLYG53lQg2LsvurRbWyF/a5fVM/oLTn5ag=="],"x5t":"H5xfs1pRtvX0HyVTskx7eTXx88U","x5t#S256":"XurVtKAIEyc4w9HCGOhnjoRHnYu4d9HCn_5YHmkScJg"},{"kid":"TV3Tl5jIY1nrJLSb53UKEubLR5gYiq9slq1SsDDg1HU","kty":"RSA","alg":"RSA-OAEP","use":"enc","n":"pNvU3ecpVHbJT4bCOEpw6cnV1yi65tB3I0bRF2ilLVOY944QRAGnjBBECPIzNbgqavghYp1j75F2nq6_ny1CYfoaxTV2iDpRUw8_f7sliYbl8FrLLat0S25ItlZrg5TEJHObvOqlG2_nXoeH36MRWwNhms2uCqfhn5VgtenIzpQIBolnM7zzGp21NvdJ1C_ZAUzkXC-l3oQ-BXTtpEVM4h2KpYh4gfZJWCbYij5d1e1YApKD6V61_Cs3Oa2OY7CAUyq5kgAWJZFDB6CpzIr226u3bV7F9RbrQu3Ybc_Lv33EwykscLznKWZY2Mbs3Iz_rFNv3sVX_vHpH4DHWlKu7Q","e":"AQAB","x5c":["MIIClzCCAX8CBgGNeYaMlzANBgkqhkiG9w0BAQsFADAPMQ0wCwYDVQQDDAR0ZXN0MB4XDTI0MDIwNTEzNDYxN1oXDTM0MDIwNTEzNDc1N1owDzENMAsGA1UEAwwEdGVzdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKTb1N3nKVR2yU+GwjhKcOnJ1dcouubQdyNG0RdopS1TmPeOEEQBp4wQRAjyMzW4Kmr4IWKdY++Rdp6uv58tQmH6GsU1dog6UVMPP3+7JYmG5fBayy2rdEtuSLZWa4OUxCRzm7zqpRtv516Hh9+jEVsDYZrNrgqn4Z+VYLXpyM6UCAaJZzO88xqdtTb3SdQv2QFM5Fwvpd6EPgV07aRFTOIdiqWIeIH2SVgm2Io+XdXtWAKSg+letfwrNzmtjmOwgFMquZIAFiWRQwegqcyK9turt21exfUW60Lt2G3Py799xMMpLHC85ylmWNjG7NyM/6xTb97FV/7x6R+Ax1pSru0CAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAQGJHeTYSMvp0yndbIn7DLohO9lom5nRrx/bLyb7TiRfogyJEF6rQZ66CAkQFk5eMF878fsHTuMVjtmXVBnhojhVmK91HwjsNQu/8xR6QMXNKJQMvHR245vwUGxlWRw/36ObM1D7QjCd/q+FonpBEY4m5Y6Uz1U0HR2Cbh0E2afVlPLeV+F0LKrlyVMdIaWBGWftCGIKDAHaG/PD66zbAKtxerv2fBIDq100WHPhd57BZxX+2aGJp1IaRDgkxV0E/CjEy3+Knd8xbAgUSW0Tl6OTC75exIvlbzeluEBe0wlapAb7WvBKYsipSW8G8Ey7tjoolDT4AU82EaKUPstiMnA=="],"x5t":"AlfHDI0FOPQpt3RBAILt0dtW1yw","x5t#S256":"a7bhm8-JsnfY7bL_m8Yl72hgmp5516VZlFcVloKzk08"}]}'
+ headers:
+ Cache-Control:
+ - no-cache
+ Content-Type:
+ - application/json;charset=UTF-8
+ Referrer-Policy:
+ - no-referrer
+ Strict-Transport-Security:
+ - max-age=31536000; includeSubDomains
+ X-Content-Type-Options:
+ - nosniff
+ X-Frame-Options:
+ - SAMEORIGIN
+ X-XSS-Protection:
+ - 1; mode=block
+ content-length:
+ - '2909'
+ status:
+ code: 200
+ message: OK
+- request:
+ body: null
+ headers:
+ Accept:
+ - '*/*'
+ Accept-Encoding:
+ - gzip, deflate
+ Authorization:
+ - Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI0VU5RQWN2VWN2LURGVU94XzRPMWd0MTNPZEpTb3RxRUtQWnVyczJ2UVc4In0.eyJleHAiOjE3MDcyMTE3NjUsImlhdCI6MTcwNzIxMTQ2NSwiYXV0aF90aW1lIjoxNzA3MjExNDY1LCJqdGkiOiIzN2E5MmJiNy1mZjViLTRkMGYtOTk2MC1kNDljOTU2YmMxMGYiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvcmVhbG1zL3Rlc3QiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiYWExMGNmYzctMmM0ZC00MWY2LThmYWMtN2JmNDA1YzU3MmM0IiwidHlwIjoiQmVhcmVyIiwiYXpwIjoidGVzdGlkIiwibm9uY2UiOiJub3QtYS1yYW5kb20tc3RyaW5nIiwic2Vzc2lvbl9zdGF0ZSI6IjVkMjg4NzkyLTJlNWEtNDBlZi1hMGFkLWIxNmM4ZmUwNTAyZCIsImFjciI6IjEiLCJhbGxvd2VkLW9yaWdpbnMiOlsiaHR0cDovLzEyNy4wLjAuMTo4MDAwIl0sInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJkZWZhdWx0LXJvbGVzLXRlc3QiLCJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJvcGVuaWQgZW1haWwgcHJvZmlsZSBrdmsgYnNuIiwic2lkIjoiNWQyODg3OTItMmU1YS00MGVmLWEwYWQtYjE2YzhmZTA1MDJkIiwia3ZrIjoiMDEyMzQ1Njc4IiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJ0ZXN0dXNlciIsImJzbiI6IjAwMDAwMDAwMCJ9.trACfjxXgErqoTdRhKSyrU08XeTZwYB_oEMn-GykJ6wBf1lUuuU5w9ahhWbA4xOdrFumZ-A732NaSg5FCAlPfXO5zvu8JlN4omlf_iL_ciTxEqhDM9p6wgGsrmxEiHO9JGWgVOXhViP_RinVHig1dniOLenvFhbPi6irhtFwPdymoCr9rnBmCHGyv6uUXlNqulrlLV3hnjoPr9BmySm1fe8_hDjXaXWCQPhZxcKC_1d6H9XixVgbV2MWQSyNnTAvpt87JxI-JZbtdzXOITXiysXLRDojFLx3NLoq4TY12C9zDjoTKQ8EE1T3PTF23YQ70lpj_m5Lr-ij3ICVx4M3Sg
+ Connection:
+ - keep-alive
+ User-Agent:
+ - python-requests/2.31.0
+ method: GET
+ uri: http://localhost:8080/realms/test/protocol/openid-connect/userinfo
+ response:
+ body:
+ string: '{"sub":"aa10cfc7-2c4d-41f6-8fac-7bf405c572c4","kvk":"012345678","email_verified":false,"preferred_username":"testuser","bsn":"000000000"}'
+ headers:
+ Cache-Control:
+ - no-cache
+ Content-Type:
+ - application/json
+ Referrer-Policy:
+ - no-referrer
+ Strict-Transport-Security:
+ - max-age=31536000; includeSubDomains
+ X-Content-Type-Options:
+ - nosniff
+ X-Frame-Options:
+ - SAMEORIGIN
+ X-XSS-Protection:
+ - 1; mode=block
+ content-length:
+ - '137'
+ status:
+ code: 200
+ message: OK
+version: 1
diff --git a/tests/conftest.py b/tests/conftest.py
index f2f4161..3fc39c0 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1,3 +1,5 @@
+from typing import Iterator
+
import pytest
from mozilla_django_oidc_db.forms import OpenIDConnectConfigForm
@@ -15,7 +17,7 @@ def mock_state_and_nonce(mocker):
@pytest.fixture
-def keycloak_config(db):
+def keycloak_config(db) -> Iterator[OpenIDConnectConfig]:
"""
Keycloak configuration for the provided docker-compose.yml setup.
@@ -46,6 +48,6 @@ def keycloak_config(db):
# in case caching is setup, ensure that it is invalidated
config.save()
- yield
+ yield config
OpenIDConnectConfig.clear_cache()
diff --git a/tests/test_integration_oidc_flow_variants.py b/tests/test_integration_oidc_flow_variants.py
index 7c074e4..cacd4dc 100644
--- a/tests/test_integration_oidc_flow_variants.py
+++ b/tests/test_integration_oidc_flow_variants.py
@@ -2,6 +2,8 @@
import pytest
+from mozilla_django_oidc_db.models import OpenIDConnectConfig
+
from .utils import keycloak_login
KEYCLOAK_BASE_URL = "http://localhost:8080/realms/test/"
@@ -38,3 +40,41 @@ def test_client_id_secret_full_flow(
assert b"client_id=testid" in token_request.body
assert b"secret=7DB3KUAAizYCcmZufpHRVOcD0TOkNO3I" in token_request.body
assert "Authorization" not in token_request.headers
+
+
+@pytest.mark.vcr
+def test_credentials_in_basic_auth_header(
+ keycloak_config: OpenIDConnectConfig,
+ mock_state_and_nonce,
+ client,
+ django_user_model,
+ vcr,
+):
+ keycloak_config.oidc_token_use_basic_auth = True
+ keycloak_config.save()
+
+ django_login_response = client.get(reverse("login"))
+ # simulate login to Keycloak
+ redirect_uri = keycloak_login(django_login_response["Location"])
+
+ # complete the login flow on our end
+ callback_response = client.get(redirect_uri)
+ assert callback_response.status_code == 302
+ assert callback_response["Location"] == "/admin/"
+
+ # check that the token request was performed as expected
+ token_request = next(
+ req
+ for req in vcr.requests
+ if req.uri == f"{KEYCLOAK_BASE_URL}protocol/openid-connect/token"
+ and req.method == "POST"
+ )
+ assert token_request is not None
+
+ assert "Authorization" in token_request.headers
+ bits = token_request.headers["Authorization"].split(" ")
+ assert len(bits) == 2
+ assert bits[0] == "Basic"
+
+ assert b"client_id=testid" in token_request.body
+ assert b"secret=" not in token_request.body