Dezenter Titel, hier nochmal aufgeschlüsselt:

  • Next.js App
  • Next-Auth gegen Entra
  • das ganze sitzt auf einem Server der nur via Proxy ins Internet kommt
    (warum ist das wichtig? Next-Auth will Profil lesen und braucht dafür Internet, die verwendete http Library kann aber keinen Proxy)
  • das alles sitzt hinter einem NGINX der reverse Proxy für App oder Container mit der App spielt
    (warum ist das wichtig? Diverse Redirects die Next-Auth und Entra machen funktionieren nur mit bestimmten NGINX Proxy Settings)

Der Witz ist dass sich tatsächlich jemand gefunden hat der das Problem mit dem (forward) Proxy gelöst hat: https://next-auth.js.org/tutorials/corporate-proxy, muss so 2022 gewesen sein und wurde auch im Juni 2025 zuletzt aktualisert funktioniert so aber nicht (mehr) – mit next-auth 4.24.13 in meinem Projekt UND der Azure Teil hat im Original und in der Änderung massives Problem wenn ein Request schief geht, nämlich keinerlei Fehlerhandling, crashed einfach raus.

Das neue node_modules/next-auth/providers/azure-ad.js sieht also so aus:

"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = AzureAD;

// NEU >
const HttpsProxyAgent = require('https-proxy-agent');
// < NEU
function AzureAD(options) {
  var _options$tenantId, _options$profilePhoto;
  const tenant = (_options$tenantId = options.tenantId) !== null && _options$tenantId !== void 0 ? _options$tenantId : "common";
  const profilePhotoSize = (_options$profilePhoto = options.profilePhotoSize) !== null && _options$profilePhoto !== void 0 ? _options$profilePhoto : 48;
  return {
    id: "azure-ad",
    name: "Azure Active Directory",
    type: "oauth",
    wellKnown: `https://login.microsoftonline.com/${tenant}/v2.0/.well-known/openid-configuration?appid=${options.clientId}`,
    authorization: {
      params: {
        scope: "openid profile email"
      }
    },
    async profile(profile, tokens) {
      var _image;
//      const response = await fetch(`https://graph.microsoft.com/v1.0/me/photos/${profilePhotoSize}x${profilePhotoSize}/$value`, {
//        headers: {
//          Authorization: `Bearer ${tokens.access_token}`
//        }
//      });
// NEU >
      let fetchOptions = {
           headers: {
              Authorization: `Bearer ${tokens.access_token}`
           }
      };
      if (process.env.http_proxy) {
        fetchOptions.agent = new HttpsProxyAgent.HttpsProxyAgent(process.env.http_proxy);
      }
      try {
         const response = await fetch(`https://graph.microsoft.com/v1.0/me/photos/${profilePhotoSize}x${profilePhotoSize}/$value`, fetchOptions);
      } catch (_unused) {}
// NEU <
      let image;
      // Zusatzprüfung auf leere response eingefügt (dort crashed er sonst raus)
      if (typeof response !== "undefined" && response.ok && typeof Buffer !== "undefined") {
        try {
          const pictureBuffer = await response.arrayBuffer();
          const pictureBase64 = Buffer.from(pictureBuffer).toString("base64");
          image = `data:image/jpeg;base64, ${pictureBase64}`;
        } catch (_unused) {}
      }
      return {
        id: profile.sub,
        name: profile.name,
        email: profile.email,
        image: (_image = image) !== null && _image !== void 0 ? _image : null
      };
    },
    style: {
      logo: "/azure.svg",
      text: "#fff",
      bg: "#0072c6"
    },
    options
  };
}

Das neue node_modules/next-auth/core/lib/oauth/client.js entspricht fast dem aus dem Artikel, nur das HttpsProxyAgent muss man auf HttpsProxyAgent.HttpsProxyAgent beim instanzieren ändern:

"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.openidClient = openidClient;

var _openidClient = require("openid-client");

var HttpsProxyAgent = require("https-proxy-agent");

async function openidClient(options) {
  const provider = options.provider;
//  if (provider.httpOptions) _openidClient.custom.setHttpOptionsDefaults(provider.httpOptions);
//  let issuer;
// NEU >
  let httpOptions = {};
  if (provider.httpOptions) httpOptions = { ...provider.httpOptions };
  if (process.env.http_proxy) {
    let agent = new HttpsProxyAgent.HttpsProxyAgent(process.env.http_proxy);
    httpOptions.agent = agent;
  }
// NEU <
  _openidClient.custom.setHttpOptionsDefaults(httpOptions);

  let issuer;
  if (provider.wellKnown) {
    issuer = await _openidClient.Issuer.discover(provider.wellKnown);
  } else {
    var _provider$authorizati, _provider$token, _provider$userinfo;
    issuer = new _openidClient.Issuer({
      issuer: provider.issuer,
      authorization_endpoint: (_provider$authorizati = provider.authorization) === null || _provider$authorizati === void 0 ? void 0 : _provider$authorizati.url,
      token_endpoint: (_provider$token = provider.token) === null || _provider$token === void 0 ? void 0 : _provider$token.url,
      userinfo_endpoint: (_provider$userinfo = provider.userinfo) === null || _provider$userinfo === void 0 ? void 0 : _provider$userinfo.url,
      jwks_uri: provider.jwks_endpoint
    });
  }
  const client = new issuer.Client({
    client_id: provider.clientId,
    client_secret: provider.clientSecret,
    redirect_uris: [provider.callbackUrl],
    ...provider.client
  }, provider.jwks);
  client[_openidClient.custom.clock_tolerance] = 10;
  return client;
}

Die NGINX Config die bei mir funktioniert:

server {
    listen 443 ssl http2;
    server_name meinserver.fqdn;

    ssl_certificate /path/mycert.pem;
    ssl_certificate_key /path/mycert.key;

    location / {
        proxy_pass http://localhost:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_cache_bypass $http_upgrade;
        proxy_read_timeout 86400;
        proxy_busy_buffers_size   512k;
        proxy_buffers   4 512k;
        proxy_buffer_size   256k;
    }

}