0

I have a Blazor application that currently uses id tokens only for authentication with an expiration of 24h. I would like to be able to use refresh tokens, but can't access them from the C# code.

However, I know that the app is correctly configured since I can get a refresh token using postman: Postman Result

Here is what the startup looks like:

services.AddAuthentication(options =>
                {
                    options.DefaultScheme = OpenIdConnectDefaults.AuthenticationScheme;
                    options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
                })
                .AddMicrosoftIdentityWebApp(options =>
                    {
                        options.SignedOutRedirectUri = "/";
                        options.SignInScheme = OpenIdConnectDefaults.AuthenticationScheme;
                        options.GetClaimsFromUserInfoEndpoint = true;
                        options.TokenValidationParameters.SaveSigninToken = true;
                        options.TokenValidationParameters.ValidateIssuer = true;
                        options.TokenValidationParameters.IssuerValidator = ValidateSpecificIssuers;
                        options.TokenValidationParameters.ValidateLifetime = true;
                        options.TokenValidationParameters.LifetimeValidator = ValidateLifetime;

                        options.Events.OnRedirectToIdentityProvider = ctx =>
                        {
                            if (ctx.Properties.Items.ContainsKey("login_hint"))
                                ctx.ProtocolMessage.LoginHint = ctx.Properties.Items["login_hint"];

                            if (ctx.Properties.Items.ContainsKey("domain_hint"))
                                ctx.ProtocolMessage.DomainHint = ctx.Properties.Items["domain_hint"];

                            var request = ctx.HttpContext.Request;
                            ctx.ProtocolMessage.RedirectUri = $"{request.Scheme}://{request.Host}/signin-oidc";

                            return Task.FromResult(0);
                        };

                        options.Events.OnAuthenticationFailed = async ctx =>
                        {
                            if (ctx.Result?.Succeeded ?? false)
                                return;

                            if (ctx.Request.Path == "/auth/logout")
                            {
                                ctx.HandleResponse();
                                return;
                            }

                            var body = await ctx.Request.Body.ReadToStringAsync();
                            var form = await ctx.Request.ReadFormAsync();

                            ctx.Principal = null;
                            ctx.HttpContext.User = null;
                            Console.WriteLine($"Authentication failed:\r\n{ctx.Exception?.Message}\r\n{ctx.Exception?.StackTrace}");
                            ctx.Response.Redirect("/auth/logout");
                            ctx.HandleResponse();
                            return;
                        };

                        options.ResponseType = OpenIdConnectResponseType.CodeIdTokenToken;
                        options.TenantId = Configuration["AzureAdB2C:TenantId"];
                        options.Instance = Configuration["AzureAdB2C:Instance"];
                        options.ClientId = Configuration["AzureAdB2C:ClientId"];
                        options.Domain = Configuration["AzureAdB2C:Domain"];
                        options.SignedOutCallbackPath = Configuration["AzureAdB2C:SignedOutCallbackPath"];
                        options.SignUpSignInPolicyId = Configuration["AzureAdB2C:SignUpSignInPolicyId"];
                        options.ResetPasswordPolicyId = Configuration["AzureAdB2C:ResetPasswordPolicyId"];
                        options.EditProfilePolicyId = Configuration["AzureAdB2C:EditProfilePolicyId"];
                        options.ClientSecret = Configuration["AzureAdB2C:ClientSecret"];
                        options.SaveTokens = true;

                        options.Scope.Add("offline_access"); // in order to get the refresh token
                    }
                )
                .EnableTokenAcquisitionToCallDownstreamApi(new string[] { Configuration["BlazorServer:Scope"] })
                .AddDownstreamWebApi("BlazorServer", configuration.GetSection("BlazorServer"))
                .AddSessionTokenCaches();

            services.Configure<OpenIdConnectOptions>(OpenIdConnectDefaults.AuthenticationScheme, (OpenIdConnectOptions options) =>
            {
                options.Scope.Add("offline_access");

                options.Events.OnTokenValidated += ctx =>
                {
                    // ctx.TokenEndpointResponse.RefreshToken is null!

                    var token = ctx.SecurityToken as JwtSecurityToken;
                    ctx.Principal.AddIdentity(new ClaimsIdentity(new Claim[]
                    {
                        new Claim("access_token", token.RawData)
                    }));

                    ctx.Principal.AddIdentity(new ClaimsIdentity(
                        token.Claims,
                        "jwt",
                        ctx.Options.TokenValidationParameters.NameClaimType,
                        ctx.Options.TokenValidationParameters.RoleClaimType
                    ));

                    return Task.CompletedTask;
                };


                options.Events.OnRemoteFailure += async (RemoteFailureContext context) =>
                {
                    var response = await context.Response.Body.ReadToStringAsync();

                    var responseQuery = response.Contains("consent_required") ? "&response=consent" : "";
                    context.Response.Redirect($"/Error?error={context.Failure}{responseQuery}");
                    
                    context.HandleResponse();
                };
            });

My ultimate goal would be to have another token claim which woud be the refresh token (I already have access token).

I've read on another post that this token could be automatically handled by Microsoft MSAL: Get refresh token with Azure AD V2.0 (MSAL) and Asp .Net Core 2.0

However, it does not appear to be the case on my end, since the user gets automatically logged out upon token expiration. Here is the token configuration I have in B2C

<Metadata>

            <Item Key="client_id">{service:te}</Item>

            <Item Key="issuer_refresh_token_user_identity_claim_type">objectId</Item>

            <Item Key="SendTokenResponseBodyWithJsonNumbers">true</Item>

            <Item Key="token_lifetime_secs">500</Item>

            <Item Key="id_token_lifetime_secs">300</Item>

            <Item Key="refresh_token_lifetime_secs">86400</Item>

            <Item Key="rolling_refresh_token_lifetime_secs">86400</Item>

          </Metadata>

I've also seend that I could call GetTokenAsync(), but in order to call that I need a cache of users. Is that the only way of doing it?

Thanks a lot for stopping by :)

vitonimal
  • 453
  • 7
  • 17

1 Answers1

1

We can use the below workaround to get refresh token for our .net application .

Below are workaround you can follow:

-Try to add the following in your appsettings.json file

"OpenIdConnect": {
"ResponseType": "code id_token token",
"Scope": [ "offline_access", "https://xxx.onmicrosoft.com/xxxx-xxc-xxx-xxxx-xxxxxx/Management" ],
"SaveTokens": "true" // Save access token and refresh token
`}

Following in your startup.cs

services.Configure<OpenIdConnectOptions>(AzureADB2CDefaults.OpenIdScheme, options =>
{
Configuration.Bind("OpenIdConnect", options);
}

For complete information please refer the below links:-

AjayKumarGhose
  • 4,257
  • 2
  • 4
  • 15