Enver Krynitski
Enver Krynitski

Reputation: 87

Silent renew access_token via refresh_token in asp.net core mvc

The problem is in automatic update access token in client part. There is next state: on client (MVC) controller I added authorize attribute and it passed well because the client use session cookie for authentication, then request is sent on server (Web API app). The server validate token and says that it has been expired. How I can renew access token in client Please see MVC Startup file:

public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
        services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
        services.AddAutoMapper(typeof(MappingProfile).Assembly);
        // Added for session state
        services.AddDistributedMemoryCache();

        services.AddSession();
        services
            .AddAuthentication(options =>
            {
                options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;

            })
            .AddCookie("Cookies")
            .AddOpenIdConnect(options =>
            {
                options.MetadataAddress = Configuration["oidc:metadataAddress"];
                options.SignInScheme = "Cookies";
                options.ClientId = Configuration["oidc:clientId"];
                options.ClientSecret = Configuration["oidc:clientSecret"];
                options.ResponseType = OpenIdConnectResponseType.CodeIdToken;
                options.CallbackPath = "/oidc-callback";
                options.GetClaimsFromUserInfoEndpoint = true;
                options.Scope.Add("openid");
                options.Scope.Add("email");
                options.Scope.Add("profile");
                options.TokenValidationParameters = new TokenValidationParameters()
                {
                    NameClaimType = "name",
                    ValidateAudience = false,
                    RoleClaimType = "role"
                };
                options.Events = new OpenIdConnectEvents
                {
                    OnTokenResponseReceived = async context=>
                    {
                        var user = context.Principal;
                        var identity = user.Identity as ClaimsIdentity;
                        var claim = new Claim("access_token", context.TokenEndpointResponse.AccessToken);
                        identity?.AddClaim(claim);
                        await Task.CompletedTask;
                    },

                };
            });
        services.AddHttpContextAccessor();
        services.AddReportServerClient();
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory logFactory)
    {
        if (env.IsDevelopment())
        {
            app.UseBrowserLink();
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
        }

        //app.UseExceptionHandlers();

        app.UseStaticFiles();

        app.UseAuthentication();


        app.UseSession();

        app.UseForwardedHeaders(new ForwardedHeadersOptions
        {
            RequireHeaderSymmetry = true,
            ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
        });

        app.UseMvc(routes =>
        {
            routes.MapRoute(
                name: "default",
                template: "{controller=Main}/{action=Index}/{id?}");
            //routes.MapRoute(
            //    name: "mainPage",
            //    template: "{controller=Main}/{action=Index}/{id?}");
        });
    }

Also tried UseTokenLifeTime for OpenIdConnectOptions, but this case doesn't work. When I remove cookie in browser and refresh the page it goes to Auth provider and give me valid token also tried

services.AddAuthentication(options =>
            {
                options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
                options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            })

Upvotes: 1

Views: 4853

Answers (1)

Phathutshedzo Khabubu
Phathutshedzo Khabubu

Reputation: 252

You can refresh an access token using multiple ways, Below I will illustrate how you can an access token using middleware in ASPNET Core.

In you startup class, in the the Configure method add the following line that will renew an access token when it is near expiration. NB add after "app.UseAuthentication()".

app.UseMiddleware<CheckAccessTokenValidityMiddleware>();

Create an extension method that will automatically renew the access token as it nears expiration as follows

public class CheckAccessTokenValidityMiddleware
{
    private readonly RequestDelegate _next;
    private readonly IConfiguration _configuration;

    public CheckAccessTokenValidityMiddleware(RequestDelegate next, IConfiguration configuration)
    {
        _next = next;
        _configuration = configuration;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        var expireAt = await context.GetTokenAsync("expires_at");
        if (expireAt != null)
        {
            var dateExpireAt = DateTime.Parse(expireAt, null, DateTimeStyles.RoundtripKind);
            if(dateExpireAt != null)
            {
                if ((dateExpireAt - DateTime.Now).TotalMinutes < 10)
                {
                    var discoveryClient = new DiscoveryClient(_configuration["OIDC:Authority"]);
                    discoveryClient.Policy.RequireHttps = false;
                    var discovery = await discoveryClient.GetAsync();
                    if (!discovery.IsError)
                    {
                        using (var tokenClient = new TokenClient(discovery.TokenEndpoint, ClientConstants.KodelessClientId, ClientConstants.KodelessClientSecret))
                        {
                            var refreshToken = await context.GetTokenAsync("refresh_token");
                            var tokenResult = await tokenClient.RequestRefreshTokenAsync(refreshToken);
                            if (!tokenResult.IsError)
                            {
                                var newIdToken = tokenResult.IdentityToken;
                                var newAccessToken = tokenResult.AccessToken;
                                var newRefreshToken = tokenResult.RefreshToken;
                                var tokens = new List<AuthenticationToken>
                                {
                                    new AuthenticationToken {Name = OpenIdConnectParameterNames.IdToken, Value = newIdToken},
                                    new AuthenticationToken
                                    {
                                        Name = OpenIdConnectParameterNames.AccessToken,
                                        Value = newAccessToken
                                    },
                                    new AuthenticationToken
                                    {
                                        Name = OpenIdConnectParameterNames.RefreshToken,
                                        Value = newRefreshToken
                                    }
                                };
                                var expiresAt = DateTime.Now + TimeSpan.FromSeconds(tokenResult.ExpiresIn);
                                tokens.Add(new AuthenticationToken
                                {
                                    Name = "expires_at",
                                    Value = expiresAt.ToString("o", CultureInfo.InvariantCulture)
                                });
                                var info = await context.AuthenticateAsync(AuthenticationConstants.Cookies);
                                info.Properties.StoreTokens(tokens);
                                await context.SignInAsync(AuthenticationConstants.Cookies, info.Principal, info.Properties);
                            }
                            else
                            {
                                await context.SignOutAsync(AuthenticationConstants.Cookies);
                                await context.SignOutAsync(AuthenticationConstants.Oidc);
                            }
                        }
                    }
                }
            }
        }
        await _next.Invoke(context);
    }
}

Upvotes: 1

Related Questions