Skip to content

feat: Private JWT CA support for passwordless - #158

Open
rmad17 wants to merge 2 commits into
mainfrom
feat/passwordless-support-ca
Open

feat: Private JWT CA support for passwordless#158
rmad17 wants to merge 2 commits into
mainfrom
feat/passwordless-support-ca

Conversation

@rmad17

@rmad17 rmad17 commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Changed

PasswordlessClient.start()

  • Removed the hardcoded "client_secret": client._client_secret field from the request body.
  • Added client._apply_client_authentication(body, f"https://{origin_domain}/", in_body=True) after the body is built, so credentials are attached by the SDK's shared helper instead of inline.

PasswordlessClient.verify()

  • Removed the same hardcoded "client_secret" field from the token-endpoint body.
  • Added client._apply_client_authentication(body, origin_issuer or f"https://{origin_domain}/", in_body=True), with the assertion audience taken from the discovery document's issuer and falling back to the
    resolved domain.

Testing
Tested via usual Passwordless flow as described in #153

@rmad17
rmad17 requested a review from a team as a code owner August 19, 2026 06:25
Comment thread src/auth0_server_python/error/__init__.py Fixed
Comment thread src/auth0_server_python/error/__init__.py Fixed
@rmad17
rmad17 changed the base branch from main to feat/passwordless-support August 19, 2026 06:27
Base automatically changed from feat/passwordless-support to main August 19, 2026 13:52
"client_secret": client._client_secret,
"connection": options.connection,
}
client._apply_client_authentication(body, f"https://{origin_domain}/", in_body=True)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both this call and the one in verify() go through _apply_client_authentication unconditionally, so a client with no secret and no signing key now hits the ConfigurationError the helper raises. Passkey signin guards the same call with a check for a configured secret or key first, because it allows public clients. Before this change, start sent client_secret as None and still reached Auth0.

If passwordless is meant to always require a confidential client, this is the right behavior and worth a line in the docstring saying so. If public clients should still work here, shall we wrap the call the way passkey does? Either way it reads better as a deliberate choice than as a side effect of the helper.

"client_secret": client._client_secret,
"connection": options.connection,
}
client._apply_client_authentication(body, f"https://{origin_domain}/", in_body=True)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The assertion audience here is built as https://{origin_domain}/, while verify() and the other token calls prefer the discovery issuer and fall back to that. start() has no discovery lookup so it can't read the issuer, and for a standard tenant the two come out the same. Just flagging the difference so it's a known choice.

assert claims["sub"] == CLIENT_ID

@pytest.mark.asyncio
async def test_start_no_client_auth_configured_raises_configuration_error(self):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pins the no-auth-configured path for start(), but verify() takes the same new ConfigurationError path and has no equivalent test. The verify assertion test above also doesn't check that a secret-configured client puts client_secret in the body. This test is copyable for both.

# ── Private Key JWT (client assertion) client authentication ────────────────


def _generate_rsa_private_key_pem() -> str:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional, and not really about this PR: these two key helpers are copied from test_server_client.py where the same definitions already live. There's no shared test-helpers module yet, so this is the third copy floating around. Might be worth pulling them into a conftest fixture at some point so they don't drift.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants