{"record":{"id":"e1b2bba5c8f594ef","repo":"nopSolutions/nopCommerce","slug":"failed-to-obtain-user-credentials-for-the-authoriz","errorCode":null,"errorMessage":"Failed to obtain user credentials for the authorization server. Check the client secrets and allow the application to perform required operations.","messagePattern":"Failed to obtain user credentials for the authorization server\\. Check the client secrets and allow the application to perform required operations\\.","errorType":"exception","errorClass":"NopException","httpStatus":null,"severity":"error","filePath":"src/Libraries/Nop.Services/Messages/SmtpBuilder.cs","lineNumber":77,"sourceCode":"        var credentialRoot = _fileProvider.Combine(tokenFilePath, emailAccount.Email);\n\n        var codeFlow = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer\n        {\n            ClientSecrets = new ClientSecrets\n            {\n                ClientId = emailAccount.ClientId,\n                ClientSecret = emailAccount.ClientSecret\n            },\n            Scopes = NopMessageDefaults.GmailScopes,\n            DataStore = new FileDataStore(credentialRoot, true)\n        });\n\n        var authCode = new AuthorizationCodeWebApp(codeFlow, null, null);\n\n        var authResult = await authCode.AuthorizeAsync(emailAccount.Email, CancellationToken.None);\n\n        if (authResult.Credential is null)\n            throw new NopException(\"Failed to obtain user credentials for the authorization server. Check the client secrets and allow the application to perform required operations.\");\n\n        if (authResult.Credential.Token?.IsStale == true)\n            await authResult.Credential.RefreshTokenAsync(CancellationToken.None);\n\n        return new SaslMechanismOAuth2(authResult.Credential.UserId, authResult.Credential.Token.AccessToken);\n    }\n\n    protected virtual async Task<SaslMechanism> GetExchangeCredentialsAsync(EmailAccount emailAccount)\n    {\n        ArgumentNullException.ThrowIfNull(emailAccount);\n\n        if (string.IsNullOrEmpty(emailAccount.ClientId))\n            throw new NopException(await _localizationService.GetResourceAsync(\"Admin.Configuration.EmailAccounts.Fields.ClientId.Required\"));\n\n        if (string.IsNullOrEmpty(emailAccount.ClientSecret))\n            throw new NopException(await _localizationService.GetResourceAsync(\"Admin.Configuration.EmailAccounts.Fields.ClientSecret.Required\"));\n\n        if (string.IsNullOrEmpty(emailAccount.TenantId))","sourceCodeStart":59,"sourceCodeEnd":95,"githubUrl":"https://github.com/nopSolutions/nopCommerce/blob/64bdf2ff08c8b39e65717bcf974fb43dc2ef68f2/src/Libraries/Nop.Services/Messages/SmtpBuilder.cs#L59-L95","documentation":"Thrown by SmtpBuilder.GetGmailCredentialsAsync after calling AuthorizationCodeWebApp.AuthorizeAsync. If the returned Credential is null, the OAuth handshake did not yield usable credentials, so the SASL mechanism cannot be built. Unlike the empty-field checks, this is a runtime OAuth failure: credentials were configured but authorization/token retrieval failed.","triggerScenarios":"Gmail OAuth where ClientId/Secret are set but AuthorizeAsync returns null Credential — e.g. consent not granted, token store corrupt/expired, revoked access, clock skew, or network failure contacting Google.","commonSituations":"User never completed the browser consent step; refresh token expired/revoked; FileDataStore token files deleted or for a different user; app not verified/published in Google Cloud; offline/network issues during token exchange.","solutions":["Re-run the OAuth consent flow so a fresh token is stored (delete the GmailAuthStore folder if corrupt).","Confirm the Google Cloud app is published or the test user is added under OAuth consent.","Verify ClientId/ClientSecret are correct and the Gmail scopes are authorized.","Check server clock synchronization and outbound HTTPS access to accounts.google.com."],"exampleFix":"// before\nvar authResult = await authCode.AuthorizeAsync(emailAccount.Email, CancellationToken.None);\nif (authResult.Credential is null)\n    throw new NopException(\"Failed to obtain user credentials...\");\n\n// after - retry consent with a hint\nvar authResult = await authCode.AuthorizeAsync(emailAccount.Email, CancellationToken.None);\nif (authResult.Credential is null)\n    throw new NopException(\"Gmail OAuth failed. Re-authorize the account in admin and retry.\");","handlingStrategy":"retry","validationCode":"// Surface a clear pre-check: presence of a stored token indicates prior consent\nvar tokenDir = _fileProvider.Combine(_fileProvider.MapPath(NopMessageDefaults.GmailAuthStorePath), emailAccount.Email);\nif (!_fileProvider.GetFiles(tokenDir, \"*.token\").Any())\n    return Error(\"Complete Gmail OAuth consent for this account before sending.\");","typeGuard":"// Cannot type-guard an external OAuth result; check the stored credential existence instead\nstatic bool HasStoredGmailToken(IFileProvider fp, string email)\n    => fp.GetFiles(fp.Combine(fp.MapPath(NopMessageDefaults.GmailAuthStorePath), email), \"*\").Any();","tryCatchPattern":"try { var sasl = await smtpBuilder.GetGmailCredentialsAsync(account); }\ncatch (NopException ex) when (ex.Message.StartsWith(\"Failed to obtain user credentials\"))\n{ /* prompt admin to re-authorize; optionally retry once after clearing the token store */ }","preventionTips":["Complete the browser consent flow once and confirm a token is stored.","If consent was revoked, re-run authorization and clear stale token files.","Publish the Google Cloud app or add the test user under OAuth consent.","Verify server clock and outbound HTTPS to Google."],"tags":["email","smtp","oauth","gmail","authentication","network","nopcommerce"],"backgroundTag":null,"analyzedSha":"64bdf2ff08c8b39e65717bcf974fb43dc2ef68f2","analyzedAt":"2026-08-13T21:19:38.062Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}