nopSolutions/nopCommerce · error · NopException
Failed to obtain user credentials for the authorization serv
Error message
Failed to obtain user credentials for the authorization server. Check the client secrets and allow the application to perform required operations.
What it means
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.
Source
Thrown at src/Libraries/Nop.Services/Messages/SmtpBuilder.cs:77
var credentialRoot = _fileProvider.Combine(tokenFilePath, emailAccount.Email);
var codeFlow = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = new ClientSecrets
{
ClientId = emailAccount.ClientId,
ClientSecret = emailAccount.ClientSecret
},
Scopes = NopMessageDefaults.GmailScopes,
DataStore = new FileDataStore(credentialRoot, true)
});
var authCode = new AuthorizationCodeWebApp(codeFlow, null, null);
var authResult = await authCode.AuthorizeAsync(emailAccount.Email, CancellationToken.None);
if (authResult.Credential is null)
throw new NopException("Failed to obtain user credentials for the authorization server. Check the client secrets and allow the application to perform required operations.");
if (authResult.Credential.Token?.IsStale == true)
await authResult.Credential.RefreshTokenAsync(CancellationToken.None);
return new SaslMechanismOAuth2(authResult.Credential.UserId, authResult.Credential.Token.AccessToken);
}
protected virtual async Task<SaslMechanism> GetExchangeCredentialsAsync(EmailAccount emailAccount)
{
ArgumentNullException.ThrowIfNull(emailAccount);
if (string.IsNullOrEmpty(emailAccount.ClientId))
throw new NopException(await _localizationService.GetResourceAsync("Admin.Configuration.EmailAccounts.Fields.ClientId.Required"));
if (string.IsNullOrEmpty(emailAccount.ClientSecret))
throw new NopException(await _localizationService.GetResourceAsync("Admin.Configuration.EmailAccounts.Fields.ClientSecret.Required"));
if (string.IsNullOrEmpty(emailAccount.TenantId))View on GitHub (pinned to 64bdf2ff08)
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.
Example fix
// before
var authResult = await authCode.AuthorizeAsync(emailAccount.Email, CancellationToken.None);
if (authResult.Credential is null)
throw new NopException("Failed to obtain user credentials...");
// after - retry consent with a hint
var authResult = await authCode.AuthorizeAsync(emailAccount.Email, CancellationToken.None);
if (authResult.Credential is null)
throw new NopException("Gmail OAuth failed. Re-authorize the account in admin and retry."); Defensive patterns
Strategy: retry
Validate before calling
// Surface a clear pre-check: presence of a stored token indicates prior consent
var tokenDir = _fileProvider.Combine(_fileProvider.MapPath(NopMessageDefaults.GmailAuthStorePath), emailAccount.Email);
if (!_fileProvider.GetFiles(tokenDir, "*.token").Any())
return Error("Complete Gmail OAuth consent for this account before sending."); Type guard
// Cannot type-guard an external OAuth result; check the stored credential existence instead
static bool HasStoredGmailToken(IFileProvider fp, string email)
=> fp.GetFiles(fp.Combine(fp.MapPath(NopMessageDefaults.GmailAuthStorePath), email), "*").Any(); Try / catch
try { var sasl = await smtpBuilder.GetGmailCredentialsAsync(account); }
catch (NopException ex) when (ex.Message.StartsWith("Failed to obtain user credentials"))
{ /* prompt admin to re-authorize; optionally retry once after clearing the token store */ } Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Admin.Configuration.EmailAccounts.Fields.ClientId.Required
- Admin.Configuration.EmailAccounts.Fields.ClientSecret.Requir
- Admin.Configuration.EmailAccounts.Fields.TenantId.Required
- Email account could not be loaded
- Email is not valid.
AI-assisted analysis of nopSolutions/nopCommerce@64bdf2ff08 (2026-08-13).
Data as JSON: /api/errors/e1b2bba5c8f594ef.
Report an issue: GitHub.