c# - How to add Refresh tokens in OpenId - Stack Overflow

admin2025-04-22  2

I am using UseOpenIdConnectAuthentication

I have added scope as offline_access but if use below snippet then context.ProtocolMessage.RefreshToken. is not being found. Can anyone please help here?

Snippet of code:

app.UseOpenIdConnectAuthentication(
    new OpenIdConnectAuthenticationOptions
    {
       
        Scope = "openid profile offline_access User.ReadBasic.All User.Read.All Directory.Read.All",
        Notifications = new OpenIdConnectAuthenticationNotifications
        {
            RedirectToIdentityProvider = (context) =>
            {

                string appBaseUrl = context.Request.Scheme + "://" + context.Request.Host + context.Request.PathBase;

                if (redirectUris.Contains(appBaseUrl.ToUpperInvariant()))
                {
                    context.ProtocolMessage.RedirectUri = appBaseUrl + "/";
                    context.ProtocolMessage.PostLogoutRedirectUri = appBaseUrl;
                }
                else
                {
                    context.ProtocolMessage.RedirectUri = redirectUris.First() + "/";
                    context.ProtocolMessage.PostLogoutRedirectUri = redirectUris.First();
                }
                return Task.FromResult(0);
            },
            SecurityTokenValidated = async context =>
            {
                try
                {
                    ClaimsIdentity claimsIdentity = context.AuthenticationTicket.Identity;

                    if (claimsIdentity.IsAuthenticated)
                    {
                        string userObjectID = claimsIdentity.FindFirst(userObjIdentifier).Value;                                 
                        if (context.AuthenticationTicket.Properties.ExpiresUtc.HasValue)
                        {
                            context.Response.Cookies.Append("AuthTokenExpiryTime", context.AuthenticationTicket.Properties.ExpiresUtc.Value.ToString());
                        }
                        var accessToken = context.ProtocolMessage.AccessToken;
                        if (!string.IsNullOrEmpty(accessToken))
                        {
                            claimsIdentity.AddClaim(new System.Security.Claims.Claim("access_token", accessToken));
                        }

                      

                       


                        //other code
                    }
                }
                catch (Exception ex)
                {
                    Trace.TraceError("Correlation ID: {0}, Exception while getting authentication token in startup.auth.cs. Source: {1}, ExceptionVerbose: {2}",
                    Trace.CorrelationManager.ActivityId,
                    ex.Source,
                    ex.ToString());
                    throw ex;
                }


            }
        }
    });

I am using UseOpenIdConnectAuthentication

I have added scope as offline_access but if use below snippet then context.ProtocolMessage.RefreshToken. is not being found. Can anyone please help here?

Snippet of code:

app.UseOpenIdConnectAuthentication(
    new OpenIdConnectAuthenticationOptions
    {
       
        Scope = "openid profile offline_access User.ReadBasic.All User.Read.All Directory.Read.All",
        Notifications = new OpenIdConnectAuthenticationNotifications
        {
            RedirectToIdentityProvider = (context) =>
            {

                string appBaseUrl = context.Request.Scheme + "://" + context.Request.Host + context.Request.PathBase;

                if (redirectUris.Contains(appBaseUrl.ToUpperInvariant()))
                {
                    context.ProtocolMessage.RedirectUri = appBaseUrl + "/";
                    context.ProtocolMessage.PostLogoutRedirectUri = appBaseUrl;
                }
                else
                {
                    context.ProtocolMessage.RedirectUri = redirectUris.First() + "/";
                    context.ProtocolMessage.PostLogoutRedirectUri = redirectUris.First();
                }
                return Task.FromResult(0);
            },
            SecurityTokenValidated = async context =>
            {
                try
                {
                    ClaimsIdentity claimsIdentity = context.AuthenticationTicket.Identity;

                    if (claimsIdentity.IsAuthenticated)
                    {
                        string userObjectID = claimsIdentity.FindFirst(userObjIdentifier).Value;                                 
                        if (context.AuthenticationTicket.Properties.ExpiresUtc.HasValue)
                        {
                            context.Response.Cookies.Append("AuthTokenExpiryTime", context.AuthenticationTicket.Properties.ExpiresUtc.Value.ToString());
                        }
                        var accessToken = context.ProtocolMessage.AccessToken;
                        if (!string.IsNullOrEmpty(accessToken))
                        {
                            claimsIdentity.AddClaim(new System.Security.Claims.Claim("access_token", accessToken));
                        }

                      

                       


                        //other code
                    }
                }
                catch (Exception ex)
                {
                    Trace.TraceError("Correlation ID: {0}, Exception while getting authentication token in startup.auth.cs. Source: {1}, ExceptionVerbose: {2}",
                    Trace.CorrelationManager.ActivityId,
                    ex.Source,
                    ex.ToString());
                    throw ex;
                }


            }
        }
    });
Share Improve this question edited Apr 14 at 15:56 Sachit Murarka asked Jan 21 at 15:03 Sachit MurarkaSachit Murarka 1831 gold badge2 silver badges13 bronze badges 20
  • Could you include more details like what you tried and where you stuck by editing your question with code and error? – Rukmini Commented Jan 22 at 5:06
  • I have added offline_access in the App Registrations. Have added scope in the code to be offline_access Previously the response type was "token id_token" . I kept it as "code id_token" now Upon debugging, I do not see access token value coming up. Also not sure how to extract refresh token – Sachit Murarka Commented Jan 22 at 7:12
  • Can you add the code by editing the question? – Rukmini Commented Jan 22 at 7:13
  • Added the snippet – Sachit Murarka Commented Jan 22 at 7:17
  • 1 Okay will check in my environment and update – Rukmini Commented Jan 22 at 7:18
 |  Show 15 more comments

1 Answer 1

Reset to default 1

Note that: UseOpenIdConnectAuthentication is obsolete. You should switch to using the newer approach, which involves configuring authentication using AddOpenIdConnect and AddAuthentication. Refer this MsDoc

To get access, ID and refresh tokens without making use of client secret, check the below:

Create a Microsoft Entra ID application and configure redirect URL under Mobile and desktop applications as https://localhost:7135/signin-oidc and enable Allow public client flows as YES:

Make sure to grant offline_access API permission:

My Startup.cs file looks like below:

namespace OpenIdConnectSample;

public class Startup
{
    public Startup(IConfiguration config, IWebHostEnvironment env)
    {
        Configuration = config;
        Environment = env;
    }

    public IConfiguration Configuration { get; set; }

    public IWebHostEnvironment Environment { get; }

    private void CheckSameSite(HttpContext httpContext, CookieOptions options)
    {
        if (options.SameSite == SameSiteMode.None)
        {
            var userAgent = httpContext.Request.Headers["User-Agent"].ToString();

            if (DisallowsSameSiteNone(userAgent))
            {
                options.SameSite = SameSiteMode.Unspecified;
            }
        }
    }

    public static bool DisallowsSameSiteNone(string userAgent)
    {
        if (string.IsNullOrEmpty(userAgent))
        {
            return false;
        }

        if (userAgent.Contains("CPU iPhone OS 12") || userAgent.Contains("iPad; CPU OS 12"))
        {
            return true;
        }

        if (userAgent.Contains("Macintosh; Intel Mac OS X 10_14") &&
            userAgent.Contains("Version/") && userAgent.Contains("Safari"))
        {
            return true;
        }

        if (userAgent.Contains("Chrome/5") || userAgent.Contains("Chrome/6"))
        {
            return true;
        }

        return false;
    }

    public void ConfigureServices(IServiceCollection services)
    {
        services.Configure<CookiePolicyOptions>(options =>
        {
            options.MinimumSameSitePolicy = SameSiteMode.Unspecified;
            options.OnAppendCookie = cookieContext => CheckSameSite(cookieContext.Context, cookieContext.CookieOptions);
            options.OnDeleteCookie = cookieContext => CheckSameSite(cookieContext.Context, cookieContext.CookieOptions);
        });

        services.AddAuthentication(sharedOptions =>
        {
            sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
        })
        .AddCookie()
        .AddOpenIdConnect(o =>
        {
            o.ClientId = "ClientID"; // Client ID

            // Removed ClientSecret as we are using PKCE
            // o.ClientSecret = "your-client-secret"; 

            o.Authority = "https://login.microsoftonline.com/TenantID/v2.0";

            o.ResponseType = OpenIdConnectResponseType.Code;
            o.SaveTokens = true;
            o.GetClaimsFromUserInfoEndpoint = true;
            o.AccessDeniedPath = "/access-denied-from-remote";
            o.ClaimsIssuer = "https://sts.windows.net/TenantID/";
            o.Scope.Add("offline_access");

            o.ClaimActions.Add(new IssuerFixupAction());

            // Enable PKCE (Proof Key for Code Exchange)
            o.UsePkce = true;

            o.Events = new OpenIdConnectEvents()
            {
                OnAuthenticationFailed = c =>
                {
                    c.HandleResponse();
                    c.Response.StatusCode = 500;
                    c.Response.ContentType = "text/plain";
                    if (Environment.IsDevelopment())
                    {
                        return c.Response.WriteAsync(c.Exception.ToString());
                    }
                    return c.Response.WriteAsync("An error occurred processing your authentication.");
                }
            };
        });
    }

    public void Configure(IApplicationBuilder app, IOptionsMonitor<OpenIdConnectOptions> optionsMonitor)
    {
        app.UseDeveloperExceptionPage();
        app.UseCookiePolicy();
        app.UseAuthentication();

        app.Run(async context =>
        {
            var response = context.Response;

            if (context.Request.Path.Equals("/signedout"))
            {
                await WriteHtmlAsync(response, async res =>
                {
                    await res.WriteAsync($"<h1>You have been signed out.</h1>");
                    await res.WriteAsync("<a class=\"btn btn-default\" href=\"/\">Home</a>");
                });
                return;
            }

            if (context.Request.Path.Equals("/signout"))
            {
                await context.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
                await WriteHtmlAsync(response, async res =>
                {
                    await res.WriteAsync($"<h1>Signed out {HtmlEncode(context.User.Identity.Name)}</h1>");
                    await res.WriteAsync("<a class=\"btn btn-default\" href=\"/\">Home</a>");
                });
                return;
            }

            if (context.Request.Path.Equals("/signout-remote"))
            {
                await context.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
                await context.SignOutAsync(OpenIdConnectDefaults.AuthenticationScheme, new AuthenticationProperties()
                {
                    RedirectUri = "/signedout"
                });
                return;
            }

            if (context.Request.Path.Equals("/access-denied-from-remote"))
            {
                await WriteHtmlAsync(response, async res =>
                {
                    await res.WriteAsync($"<h1>Access Denied error received from the remote authorization server</h1>");
                    await res.WriteAsync("<a class=\"btn btn-default\" href=\"/\">Home</a>");
                });
                return;
            }

            if (context.Request.Path.Equals("/Account/AccessDenied"))
            {
                await context.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
                await WriteHtmlAsync(response, async res =>
                {
                    await res.WriteAsync($"<h1>Access Denied for user {HtmlEncode(context.User.Identity.Name)} to resource '{HtmlEncode(context.Request.Query["ReturnUrl"])}'</h1>");
                    await res.WriteAsync("<a class=\"btn btn-default\" href=\"/signout\">Sign Out</a>");
                    await res.WriteAsync("<a class=\"btn btn-default\" href=\"/\">Home</a>");
                });
                return;
            }

            var userResult = await context.AuthenticateAsync();
            var user = userResult.Principal;
            var props = userResult.Properties;

            // Not authenticated
            if (user == null || !user.Identities.Any(identity => identity.IsAuthenticated))
            {
                await context.ChallengeAsync();
                return;
            }

            if (context.Request.Path.Equals("/restricted") && !user.Identities.Any(identity => identity.HasClaim("special", "true")))
            {
                await context.ForbidAsync();
                return;
            }

            if (context.Request.Path.Equals("/refresh"))
            {
                var refreshToken = props.GetTokenValue("refresh_token");

                if (string.IsNullOrEmpty(refreshToken))
                {
                    await WriteHtmlAsync(response, async res =>
                    {
                        await res.WriteAsync($"No refresh_token is available.<br>");
                        await res.WriteAsync("<a class=\"btn btn-link\" href=\"/signout\">Sign Out</a>");
                    });

                    return;
                }
            }

            if (context.Request.Path.Equals("/login-challenge"))
            {
                await context.ChallengeAsync(OpenIdConnectDefaults.AuthenticationScheme, new OpenIdConnectChallengeProperties()
                {
                    Prompt = "login",
                    Scope = new string[] { "openid", "profile", "offline_access" }
                });

                return;
            }

            await WriteHtmlAsync(response, async res =>
            {
                await res.WriteAsync($"<h1>Hello Authenticated User {HtmlEncode(user.Identity.Name)}</h1>");
                await res.WriteAsync("<a class=\"btn btn-default\" href=\"/restricted\">Restricted</a>");
                await res.WriteAsync("<a class=\"btn btn-default\" href=\"/login-challenge\">Login challenge</a>");
                await res.WriteAsync("<a class=\"btn btn-default\" href=\"/signout\">Sign Out</a>");
                await res.WriteAsync("<a class=\"btn btn-default\" href=\"/signout-remote\">Sign Out Remote</a>");

                await res.WriteAsync("<h2>Claims:</h2>");
                await WriteTableHeader(res, new string[] { "Claim Type", "Value" }, context.User.Claims.Select(c => new string[] { c.Type, c.Value }));

                await res.WriteAsync("<h2>Tokens:</h2>");
                await WriteTableHeader(res, new string[] { "Token Type", "Value" }, props.GetTokens().Select(token => new string[] { token.Name, token.Value }));
            });
        });
    }

    private static async Task WriteHtmlAsync(HttpResponse response, Func<HttpResponse, Task> writeContent)
    {
        var bootstrap = "<link rel=\"stylesheet\" href=\"https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css\" integrity=\"sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu\" crossorigin=\"anonymous\">";

        response.ContentType = "text/html";
        await response.WriteAsync($"<html><head>{bootstrap}</head><body><div class=\"container\">");
        await writeContent(response);
        await response.WriteAsync("</div></body></html>");
    }

    private static async Task WriteTableHeader(HttpResponse response, IEnumerable<string> columns, IEnumerable<IEnumerable<string>> data)
    {
        await response.WriteAsync("<table class=\"table table-condensed\">");
        await response.WriteAsync("<tr>");
        foreach (var column in columns)
        {
            await response.WriteAsync($"<th>{HtmlEncode(column)}</th>");
        }
        await response.WriteAsync("</tr>");
        foreach (var row in data)
        {
            await response.WriteAsync("<tr>");
            foreach (var column in row)
            {
                await response.WriteAsync($"<td>{HtmlEncode(column)}</td>");
            }
            await response.WriteAsync("</tr>");
        }
        await response.WriteAsync("</table>");
    }

    private static string HtmlEncode(string content) =>
        string.IsNullOrEmpty(content) ? string.Empty : HtmlEncoder.Default.Encode(content);

    private class IssuerFixupAction : ClaimAction
    {
        public IssuerFixupAction() : base(ClaimTypes.NameIdentifier, string.Empty) { }

        public override void Run(JsonElement userData, ClaimsIdentity identity, string issuer)
        {
            var oldClaims = identity.Claims.ToList();
            foreach (var claim in oldClaims)
            {
                identity.RemoveClaim(claim);
                identity.AddClaim(new Claim(claim.Type, claim.Value, claim.ValueType, issuer, claim.OriginalIssuer, claim.Subject));
            }
        }
    }
}

When I run the project I got sign-in screen as below:

After sign-in access, ID and refresh token got generated successfully:

You can also refresh the access token refer the below GitHub blog:

aspnetcore/src/Security/Authentication/OpenIdConnect/samples/OpenIdConnectSample/Startup.cs at main · dotnet/aspnetcore · GitHub by josephdecock.

转载请注明原文地址:http://anycun.com/QandA/1745302036a90590.html