{"record":{"id":"83634a30ed575615","repo":"bitwarden/server","slug":"invalid-response-from-teams","errorCode":null,"errorMessage":"Invalid response from Teams.","messagePattern":"Invalid response from Teams\\.","errorType":"exception","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"src/Api/Dirt/Controllers/TeamsIntegrationController.cs","lineNumber":119,"sourceCode":"        {\n            throw new NotFoundException();\n        }\n\n        var callbackUrl = Url.RouteUrl(\n            routeName: \"TeamsIntegration_Create\",\n            values: null,\n            protocol: currentContext.HttpContext.Request.Scheme,\n            host: currentContext.HttpContext.Request.Host.ToUriComponent()\n        );\n        if (string.IsNullOrEmpty(callbackUrl))\n        {\n            throw new BadRequestException(\"Unable to build callback Url\");\n        }\n\n        var token = await teamsService.ObtainTokenViaOAuth(code, callbackUrl);\n        if (string.IsNullOrEmpty(token))\n        {\n            throw new BadRequestException(\"Invalid response from Teams.\");\n        }\n\n        var teams = await teamsService.GetJoinedTeamsAsync(token);\n\n        if (!teams.Any())\n        {\n            throw new BadRequestException(\"No teams were found.\");\n        }\n\n        var teamsIntegration = new TeamsIntegration(TenantId: teams[0].TenantId, Teams: teams);\n        integration.Configuration = JsonSerializer.Serialize(teamsIntegration);\n        await integrationRepository.UpsertAsync(integration);\n\n        var location = $\"/organizations/{integration.OrganizationId}/integrations/{integration.Id}\";\n        return Created(location, new OrganizationIntegrationResponseModel(integration));\n    }\n\n    [Route(\"integrations/teams/incoming\")]","sourceCodeStart":101,"sourceCodeEnd":137,"githubUrl":"https://github.com/bitwarden/server/blob/e93b962371d80964556f5590c6615f5160a437a1/src/Api/Dirt/Controllers/TeamsIntegrationController.cs#L101-L137","documentation":"Thrown during the Teams OAuth callback when teamsService.ObtainTokenViaOAuth returns null or empty after exchanging the authorization code with Microsoft. The token exchange failed — code expired, invalid, or Microsoft returned an error. BadRequestException returns HTTP 400.","triggerScenarios":"OAuth callback with a stale/already-used/invalid authorization code; Microsoft Entra ID (Azure AD) app credentials misconfigured; redirect URI mismatch; network failure during server-to-Microsoft token exchange.","commonSituations":"User delays authorization so the code expires; duplicate callback consumption; Azure AD app client secret rotated but not updated in config; redirect URI registered in Azure portal doesn't match the deployed callback; transient Microsoft Graph API outage.","solutions":["Exchange the authorization code immediately after receiving it.","Verify Azure AD app client_id/client_secret are correct in server configuration.","Ensure the redirect URI registered in Azure AD matches the deployed callback URL exactly.","Add retry logic for transient network failures during token exchange."],"exampleFix":"// before\nvar token = await teamsService.ObtainTokenViaOAuth(code, callbackUrl);\nif (string.IsNullOrEmpty(token)) throw new BadRequestException(\"Invalid response from Teams.\");\n\n// after — capture and surface the underlying error\nvar result = await teamsService.ObtainTokenViaOAuthAsync(code, callbackUrl);\nif (!result.Success)\n{\n    _logger.LogError(\"Teams token exchange failed: {Error}\", result.ErrorDescription);\n    throw new BadRequestException($\"Teams OAuth failed: {result.Error}\");\n}","handlingStrategy":"retry","validationCode":"// Pre-check Azure AD app configuration\nif (string.IsNullOrWhiteSpace(_config[\"Teams:ClientId\"]) ||\n    string.IsNullOrWhiteSpace(_config[\"Teams:ClientSecret\"]) ||\n    string.IsNullOrWhiteSpace(_config[\"Teams:TenantId\"]))\n    throw new InvalidOperationException(\"Teams OAuth credentials not configured.\");","typeGuard":"public static bool TeamsCredentialsConfigured(IConfiguration config) =>\n    !string.IsNullOrEmpty(config[\"Teams:ClientId\"]) &&\n    !string.IsNullOrEmpty(config[\"Teams:ClientSecret\"]);","tryCatchPattern":"int attempts = 0;\nstring? token = null;\nwhile (attempts < 3 && string.IsNullOrEmpty(token))\n{\n    try { token = await _teamsService.ObtainTokenViaOAuth(code, callbackUrl); }\n    catch (Exception ex) when (attempts < 2) { _logger.LogWarning(\"Teams token attempt {N} failed: {Ex}\", attempts, ex.Message); }\n    attempts++;\n}\nif (string.IsNullOrEmpty(token))\n    return BadRequest(\"Teams OAuth token exchange failed. Please retry the authorization flow.\");","preventionTips":["Exchange the authorization code immediately after receiving the callback.","Ensure the redirect URI in Azure AD matches the deployed callback URL exactly.","Keep client secrets in secure configuration and rotate regularly.","Add retry logic for transient Microsoft Graph outages."],"tags":["teams","oauth","token-exchange","configuration","azure-ad","network","csharp","aspnet"],"backgroundTag":null,"analyzedSha":"e93b962371d80964556f5590c6615f5160a437a1","analyzedAt":"2026-08-13T14:22:19.382Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}