Skip to content

Server crashes with opaque Internal Error on startup when [server.auth.jwk].endpoint points at a Microsoft Entra (Azure AD) JWKS #60

Description

@theRTC204

Summary

When [server.auth] is configured to validate JWTs against a Microsoft Entra ID (Azure AD) JWKS endpoint, loreserver exits during startup with Error: Internal Error and an unsymbolicated backtrace. No log line indicates the cause. The same configuration starts successfully when the JWKS endpoint is pointed at a different OIDC provider (e.g. Google), which isolates the failure to the contents of Entra's JWKS response rather than the rest of the auth configuration.

Environment

  • Lore Server version: 0.8.3+201
  • OS: Linux (x86-64)
  • Identity provider: Microsoft Entra ID (Azure AD), v2.0 endpoints, RS256-signed tokens

Configuration to reproduce

[server.auth]
jwt_issuer   = "https://login.microsoftonline.com/<tenant-id>/v2.0"
jwt_audience = ["<application-client-id>"]

[server.auth.jwk]
endpoint = "https://login.microsoftonline.com/<tenant-id>/discovery/v2.0/keys"

Steps to reproduce

  1. Configure [server.auth] as above with a real Entra tenant id, application (client) id, and the tenant's v2.0 JWKS endpoint.
  2. Start loreserver.

Expected behavior

The server loads the signing keys from the JWKS endpoint and starts with authentication enabled.

Actual behavior

The server exits during startup. The only error output is:

Error: Internal Error

Stack backtrace:
   0: <unknown>
   1: <unknown>
   ...

Run with RUST_LOG=trace and RUST_BACKTRACE=1, the trace confirms the JWKS endpoint is reached and a response is received successfully (TLS completes against the genuine login.microsoftonline.com certificate, the response body is read, and the connection is returned to the pool). The urc.auth.jwk_service meter is created, and the Internal Error is emitted immediately afterward. No warn!/error! line is produced explaining the failure.

Logs

Redacted RUST_LOG=trace / RUST_BACKTRACE=1 excerpt (TLS handshake frames and unrelated startup lines removed; the JWKS fetch completes, then the error is emitted):

// ... store wiring elided ...
{"log.level":"DEBUG","log.logger":"reqwest::connect","message":"starting new connection: https://login.microsoftonline.com/"}
{"log.level":"DEBUG","log.logger":"hyper_util::client::legacy::connect::http","message":"connecting to [<redacted-ipv6>]:443"}
{"log.level":"DEBUG","log.logger":"hyper_util::client::legacy::connect::http","message":"connected to [<redacted-ipv6>]:443"}
// ... rustls TLS 1.3 handshake frames elided (genuine login.microsoftonline.com cert chain) ...
{"log.level":"TRACE","log.logger":"reqwest::retry","message":"shouldn't retry!"}
{"log.level":"DEBUG","log.logger":"opentelemetry_sdk","meter_name":"urc.auth.jwk_service","name":"MeterProvider.NewMeterCreated"}
{"log.level":"DEBUG","log.logger":"hyper_util::client::legacy::pool","message":"pooling idle connection for (\"https\", login.microsoftonline.com)"}
{"log.level":"DEBUG","log.logger":"rustls::common_state","message":"Sending warning alert CloseNotify"}
Error: Internal Error

Stack backtrace:
   0: <unknown>
   1: <unknown>
   2: <unknown>
   3: <unknown>
   4: <unknown>
   5: <unknown>
   6: __libc_start_call_main
             at ./csu/../sysdeps/nptl/libc_start_call_main.h:58:16
   7: __libc_start_main_impl
             at ./csu/../csu/libc-start.c:392:3
   8: <unknown>

The HTTPS fetch succeeds (note pooling idle connection, which only happens after the response body is fully read); the error is emitted immediately after the urc.auth.jwk_service meter is created — i.e. while processing the fetched keys, not while fetching them.

Isolation / control test

Changing only [server.auth.jwk].endpoint to https://www.googleapis.com/oauth2/v3/certs (leaving jwt_issuer/jwt_audience unchanged) allows the server to boot past auth initialization:

Lore Public QUIC Server: Auth: yes
Starting Lore HTTP Server: 0.0.0.0:41339, Auth: yes
Starting Lore GRPC Server: 0.0.0.0:41337, Auth: enabled

This indicates the failure is specific to the JWKS payload returned by Entra, not the surrounding configuration.

Root cause

The failure occurs in lore-server/src/auth/jwk.rs, in JwkServiceImpl::fetch_new_keys(), while iterating the fetched keys:

let algorithm = jwk
    .common
    .key_algorithm
    .ok_or(JWKServiceError::InternalError)?;

jwk.common.key_algorithm corresponds to the JWK alg member, which is treated as required. Microsoft Entra's JWK entries do not include an alg field, so key_algorithm is None and the function returns JWKServiceError::InternalError.

JWKServiceError::InternalError has a Display implementation of the literal string "Internal Error":

#[derive(Error, Debug)]
pub enum JWKServiceError {
    #[error("Internal Error")]
    InternalError,
    ...
}

This error is surfaced to the user because the initial key fetch at startup propagates it with ? (in lore-server/src/server.rs, around line 1713):

let jwk_service = JwkServiceImpl::new(jwk.clone());
jwk_service
    .fetch_new_keys(None /* fetch all keys */)
    .await?;

Unlike the non-success-status and JSON-parse branches earlier in fetch_new_keys (which call warn! before returning), the missing-alg path returns InternalError without logging, so the underlying cause is not visible in the logs.

Observed JWKS field differences

Fields present in a Microsoft Entra v2.0 key object (https://login.microsoftonline.com/<tenant-id>/discovery/v2.0/keys):

Field Present
kty (RSA) yes
use (sig) yes
kid yes
x5t yes
n, e yes
x5c yes
cloud_instance_name yes (Entra-specific)
issuer yes (Entra-specific)
alg no

Google's keys, by contrast, include "alg": "RS256", which is why pointing the same configuration at Google's endpoint succeeds. Entra's OIDC discovery document advertises id_token_signing_alg_values_supported: ["RS256"].

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions