Интеграция ASP.NET Core Identity и Microsoft Graph: проблемы с истечением срока действия токенаC#

Место общения программистов C#
Ответить
Anonymous
 Интеграция ASP.NET Core Identity и Microsoft Graph: проблемы с истечением срока действия токена

Сообщение Anonymous »

У нас есть веб-приложение ASP.NET Core с двумя потоками аутентификации:
  • ASP.NET Core Identity для стандартной аутентификации пользователя
  • Внешняя проверка подлинности с помощью Microsoft 365
Мы работаем над новой функцией, которая позволяет пользователям отправлять электронные письма через Microsoft Graph при входе в систему. с использованием внешней аутентификации Microsoft 365. Для этого мы используем делегированные разрешения, сохраняя токены Microsoft Graph в базе данных SQL, а не в кеше памяти, чтобы избежать их потери во время перезапуска или сбоя приложения.
Поток внешнего входа работает должным образом: и пользователи могут успешно отправлять электронные письма через Microsoft Graph, однако через некоторое время токен Microsoft Graph удаляется из базы данных, хотя пользователь все еще вошел в приложение. В результате отправка электронных писем завершается сбоем из-за ошибки, связанной с отсутствием токена.
Я не ожидал такого поведения, хотя предполагалось, что за сценой произошло своего рода автоматическое обновление токена. Кроме того, когда происходит это исключение, пользователь все еще находится в системе.
Кто-нибудь сталкивался с подобной проблемой или имеет предложения по ее решению? Любые идеи или советы будут очень признательны. Ниже приведен соответствующий код, который мы уже реализовали для справки.

Код: Выделить всё

appsettings.json
конфигурация:

Код: Выделить всё

"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "[Enter the domain of your tenant, e.g.  contoso.onmicrosoft.com]",
"CallbackPath": "/signin-oidc",
"SignedOutCallbackPath": "/signout-callback-oidc",

"IsEnabled": true
},
"MicrosoftGraph": {
"BaseUrl": "https://graph.microsoft.com/v1.0",
"Scopes": "mail.send"
},

Код: Выделить всё

Startup.cs
код:

Код: Выделить всё

services.AddDefaultIdentity(options => options.SignIn.RequireConfirmedAccount = true)
.AddRoles()
.AddEntityFrameworkStores()
.AddErrorDescriber();

services.Configure(options =>
{
// Password settings
options.Password.RequireDigit = true;
options.Password.RequiredLength = 8;
options.Password.RequireNonAlphanumeric = true;
options.Password.RequireUppercase = true;
options.Password.RequireLowercase = true;

// Lockout settings
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromHours(8);
options.Lockout.MaxFailedAccessAttempts = 10;
options.Lockout.AllowedForNewUsers = true;

// User settings
options.User.RequireUniqueEmail = false;
});

services.ConfigureApplicationCookie(config =>
{
config.LoginPath = "/Home/Login";
});

services.AddDistributedSqlServerCache(options =>
{
options.ConnectionString = connectionString;
options.SchemaName = "dbo";
options.TableName = "TbDistributedTokenCache";
});

services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromMinutes(20);
options.Cookie.HttpOnly = false;
options.Cookie.IsEssential = false;
});

try
{
var conf = Configuration.GetSection("AzureAd");

if (conf != null && Boolean.Parse(conf["IsEnabled"]))
{
var credentials = GetAzureAuthCredentials();

if (credentials != null)
{
var initialScopes = Configuration["MicrosoftGraph:Scopes"]?.Split(' ');
services.AddAuthentication()
.AddMicrosoftIdentityWebApp(opt =>
{
opt.ClaimActions.MapAll();
opt.GetClaimsFromUserInfoEndpoint = true;
opt.ClientId = credentials.ClientId;
opt.TenantId = credentials.TenantId;
opt.ClientSecret = credentials.ClientSecret;
opt.CallbackPath = conf["CallbackPath"];
opt.SignedOutCallbackPath = conf["SignedOutCallBackPath"];
opt.Domain = conf["Domain"];
opt.Instance = conf["Instance"];

}, cookieScheme: null
// Solves cookieScheme conflict with default aspnetcore identity cookiescheme
//https://github.com/AzureAD/microsoft-identity-web/issues/133

)
.EnableTokenAcquisitionToCallDownstreamApi(initialScopes)
.AddMicrosoftGraph(Configuration.GetSection("MicrosoftGraph"))
.AddDistributedTokenCaches();
services.AddScoped();

}
}

}
catch (Exception) { }

Код: Выделить всё

DelegatedO365Manager
класс:

Код: Выделить всё

public class DelegatedOffice365Manager : IOffice365Manager
{
private readonly GraphServiceClient _graphClient;

private Recipient[] ParseRecipients(string recipientString)
{
if (string.IsNullOrWhiteSpace(recipientString))
{
return Array.Empty();
}

return recipientString
.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
.Select(email => new Recipient
{
EmailAddress = new EmailAddress
{
Address = email.Trim()
}
})
.ToArray();
}

public DelegatedOffice365Manager(GraphServiceClient graphClient)
{
_graphClient = graphClient;
}

public async Task  SendEmailAsync(Office365EmailMessage email)
{
try
{
var message = new Message
{
From = new Recipient
{
EmailAddress = new EmailAddress
{
Address = email.FromAddress
}
},
Subject = email.Subject,
Body = new ItemBody
{
ContentType = BodyType.Html,
Content = email.BodyContent
},
ToRecipients = ParseRecipients(email.ToRecipients),
CcRecipients = ParseRecipients(email.CcRecipients),
BccRecipients = ParseRecipients(email.BccRecipients),
};

await _graphClient.Me
.SendMail(message, email.SaveToSentItems)
.Request()
.WithAuthenticationScheme(OpenIdConnectDefaults.AuthenticationScheme)
.PostAsync();

return (true, string.Empty);
}
catch (Exception ex)
{
return (false, ex.Message);
}
}
}
Метод действия контроллера:

Код: Выделить всё

[AuthorizeForScopes(Scopes = new string[] { "mail.send" }, AuthenticationScheme = OpenIdConnectDefaults.AuthenticationScheme)]
public async Task SendMailWithOffice365(O365EmailMessage email)
{
if (_o365Manager == null)
{
return new JsonResult(new { statusCode = -1, message = "Graph client not initialized" });
}

if (User.GetMsalAccountId() == null)
{
return new JsonResult(new { statusCode = -2, message = "User not authenticated with Office 365" });
}

var result = await _o365Manager.SendEmailAsync(email);

if (result.Success)
{
return new JsonResult(new { statusCode = 0, message = "OK" });
}
else
{
return new JsonResult(new { statusCode = -3, message = result.ErrorMessage });
}
}
Код JavaScript:

Код: Выделить всё

function sendMailW365(params) {
// Define the URL endpoint for sending mail with Office 365
let url = baseUrl + 'api/data/SendMailWithOffice365';

$.ajax({
url: url,
data: params,
headers: { 'x-ReturnUrl': '/' },
type: "POST",
cache: false,
contentType: 'application/json',

success: function (response) {
let statusCode = response.statusCode;
let message = response.message;

// Handle different status codes
switch (statusCode) {
case 0:
// If status code is 0, mail was sent successfully
appendMessage('Send Mail', 'E-Mail sent successfully!', 'INF', 10000);
break;
case -2:
// If status code is -2, user is not authenticated with Office 365
appendMessage('Send Mail', 'User not authenticated with Office 365', 'ALR', 10000);
break;
case -3:
// If status code is -3, there was an error sending the mail, prompt re-login
appendMessage('Send Mail', "Error sending the mail. Please re-login using Office 365.", 10000);
console.log(message); // Log the error message
break;
}
},

// On error during the request
error: function (xhr, status, error) {
console.error("Error sending the mail:", error);
appendMessage('Send Mail', "Error sending the mail! Mail not sent.", 'ALR', 10000);

// Redirect to the location provided in the response header
window.location = xhr.getResponseHeader("Location");
}
});
}
Я тщательно просмотрел документацию и поискал в Интернете похожие проблемы, но не смог найти решения.

Подробнее здесь: https://stackoverflow.com/questions/790 ... en-expirat
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «C#»