Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

.Net Project Graph SDK Token is expiring when deployed to Azure App Service #3106

Open
ricardomatos95 opened this issue Oct 24, 2024 · 2 comments

Comments

@ricardomatos95
Copy link

Microsoft.Identity.Web Library

Microsoft.Identity.Web

Microsoft.Identity.Web version

3.2.2

Web app

Sign-in users and call web APIs

Web API

Protected web APIs (validating tokens)

Token cache serialization

In-memory caches

Description

I developed a .NET webapp and after deploying it into Azure App servicey the access token seems to be expiring after 1h and not refreshing and throwing me the error:

ODataError: Lifetime validation failed, the token is expired.

Currently I have a class called Invite.cshtml.cs that looks like this:

namespace Test_Web_App.Pages
{
    [AuthorizeForScopes(ScopeKeySection = "MicrosoftGraph:Scopes")]
    public class InviteModel : PageModel
    {
        private readonly GraphServiceClient _graphServiceClient;
        private readonly ILogger<IndexModel> _logger;
        private readonly IConfiguration _configuration;
        private readonly IHttpContextAccessor _httpContextAccessor;

        public InviteModel(  ILogger<IndexModel> logger, IConfiguration configuration, GraphServiceClient graphServiceClient, IHttpContextAccessor httpContextAccessor)
        {
            _logger = logger;
            _graphServiceClient = graphServiceClient; ;
            _configuration = configuration;
            _httpContextAccessor = httpContextAccessor;
        }

public async Task<IActionResult> OnPostAsync()
{
        var user = await _graphServiceClient.Me.GetAsync(); ;
//Other actions
 }

The code after 1 hour of the user being signed in throws a server error for token expiration on my OnPostAsync method as soon as it tries to load the graphServiceClient:

public async Task<IActionResult> OnPostAsync()
{
        var user = await _graphServiceClient.Me.GetAsync(); ;
}

On my Program.cs I have setup the following:

var builder = WebApplication.CreateBuilder(args);

var initialScopes = builder.Configuration["AzureAd:Scopes"]?.Split(' ') ?? builder.Configuration["MicrosoftGraph:Scopes"]?.Split(' ');

// Add services to the container.
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp( options =>
    {
        builder.Configuration.Bind("AzureAd", options);
        options.SaveTokens = true; // Ensure tokens are saved
    })
        .EnableTokenAcquisitionToCallDownstreamApi(initialScopes)
            .AddInMemoryTokenCaches().AddMicrosoftGraph();

builder.Services.AddAuthorization(options =>
{
    // By default, all incoming requests will be authorized according to the default policy.
    options.FallbackPolicy = options.DefaultPolicy;
});
builder.Services.AddRazorPages().AddMicrosoftIdentityUI();
var app = builder.Build();

Reproduction steps

  1. Create builder for Initialize GraphServiceClient.
  2. Create class to send call to graphServiceClient (e.g., graphServiceClient.Me.GetAsync(); )
  3. Publish application to Azure Web App
  4. Open Azure Web app trigger Post method.
  5. Wait 1 hour for token to expire and trigger post method again

Error message

ODataError: Lifetime validation failed, the token is expired.

Microsoft.Kiota.Http.HttpClientLibrary.HttpClientRequestAdapter.ThrowIfFailedResponseAsync(HttpResponseMessage response, Dictionary<string, ParsableFactory> errorMapping, Activity activityForAttributes, CancellationToken cancellationToken)
Microsoft.Kiota.Http.HttpClientLibrary.HttpClientRequestAdapter.SendAsync(RequestInformation requestInfo, ParsableFactory factory, Dictionary<string, ParsableFactory> errorMapping, CancellationToken cancellationToken)
Microsoft.Kiota.Http.HttpClientLibrary.HttpClientRequestAdapter.SendAsync(RequestInformation requestInfo, ParsableFactory factory, Dictionary<string, ParsableFactory> errorMapping, CancellationToken cancellationToken)
Microsoft.Graph.Me.MeRequestBuilder.GetAsync(Action<RequestConfiguration> requestConfiguration, CancellationToken cancellationToken)
External_Guest_Web_App.Pages.InviteGuestModel.OnPostAsync() in InviteGuest.cshtml.cs
Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.ExecutorFactory+GenericTaskHandlerMethod.Convert(object taskAsObject)
Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.ExecutorFactory+GenericTaskHandlerMethod.Execute(object receiver, object[] arguments)
Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.InvokeHandlerMethodAsync()
Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.InvokeNextPageFilterAsync()
Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.Rethrow(PageHandlerExecutedContext context)
Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted)
Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.InvokeInnerFilterAsync()
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|26_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, object state, bool isCompleted)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ExceptionContextSealed context)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|25_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, object state, bool isCompleted)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, object state, bool isCompleted)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Logged|17_1(ResourceInvoker invoker)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Logged|17_1(ResourceInvoker invoker)
Microsoft.AspNetCore.Routing.EndpointMiddleware.g__AwaitRequestTask|7_0(Endpoint endpoint, Task requestTask, ILogger logger)
Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)

Id Web logs

No response

Relevant code snippets

Program.cs:

// Add services to the container.
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp( options =>
    {
        builder.Configuration.Bind("AzureAd", options);
        options.SaveTokens = true; // Ensure tokens are saved
    })
        .EnableTokenAcquisitionToCallDownstreamApi(initialScopes)
            .AddInMemoryTokenCaches().AddMicrosoftGraph();


 Invite.cshtml.cs: 
namespace Test_Web_App.Pages
{
    [AuthorizeForScopes(ScopeKeySection = "MicrosoftGraph:Scopes")]
    public class InviteModel : PageModel
    {
        private readonly GraphServiceClient _graphServiceClient;
        private readonly ILogger<IndexModel> _logger;
        private readonly IConfiguration _configuration;
        private readonly IHttpContextAccessor _httpContextAccessor;

        public InviteModel(  ILogger<IndexModel> logger, IConfiguration configuration, GraphServiceClient graphServiceClient, IHttpContextAccessor httpContextAccessor)
        {
            _logger = logger;
            _graphServiceClient = graphServiceClient; ;
            _configuration = configuration;
            _httpContextAccessor = httpContextAccessor;
        }

public async Task<IActionResult> OnPostAsync()
{
        var user = await _graphServiceClient.Me.GetAsync(); ;
//Other actions
 }

Regression

No response

Expected behavior

I expected the builder settings in Program.cs to automatically refresh the Access token when making requests to graph API.

@TanguyPa
Copy link

I seem to have a similar issue, also on my side the refresh token never seems to be used.
The difference is that I don't get an error but simply a redirection to the identity provider (login.microsoft.com) to retrieve a new access token and another token id. I'm using ASP MVC in .NET 8.
I've tried inmemory cache and distributed cache with SQL Server, considerably increasing cookie and/or cache expiration.
For my part, I expect the access token or id token to be renewed with the refresh token when the latter is used, and the redirection to take place only when the refresh token is not usable.
I've analyzed the information stored in debug and in my cache, and I do have a refresh token, but it's not being used.

@ricardomatos95
Copy link
Author

@TanguyPa Well on my case I solved it after checking this:

#2880

Was indeed the "easy auth" or App Service Authentication being enabled from Azure App Service that was causing it. After disabling it it just used my code for authentication and seems to work perfectly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants