Closed Good-man closed 4 years ago
@Good-man thanks for submitting this issue. This isn't supported in the SDK today but our team is actively looking into how we can officially support this in the SDK and if there is a workaround we can suggest.
We'll get back to you in a couple of weeks once we have more information.
This isn't supported in the SDK today but our team is actively looking into how we can officially support this in the SDK and if there is a workaround we can suggest.
Totally understand! I would love a suggested workaround! Thank you!
Hi @chrismorris-okta,
I am able to retrieve a new Access Token using the Refresh Token, but where in the Okta/Owin/OIDC pipeline do I reintroduce the new tokens?
Mark
I posted a related issue on StackOverflow.
How does a client using Owin/Katana/OIDC use a Refresh Token?
Thanks for the follow-up Mark. I'm tagging @laura-rodriguez to help answer this since she's digging into this area this week
Hi @Good-man,
Thanks for your question.
The user session lifetime is separate from access tokens' lifetime. Access tokens are usually used by a resource server to authorize a request. The OIDC SDK is responsible for the authentication and retrieving of the tokens, but then it is up to the client (your app) to decide how they manage their sessions or authorize resources.
That being said, based on your comment Our use-case is a feature that we call "keep me logged in" where our customers can continue to access authenticated, but non-sensitive resources when they return to the site for weeks or months (similar to Amazon and Facebook)., it seems your session is already independent of the access token's lifetime. Is that correct? If so, maybe you can write your own Authorization filter that does the token verification for you (tokens are stored as user claims), and apply these to your protected endpoints, something like this:
I haven't tried this by myself, but maybe this could be an option to explore.
Are your protected endpoints part of your MVC application? Or are they in a different Web API project? Do you have any code to share with us to see what option you are currently exploring and where are you getting stuck?
Hi @laura-rodriguez ,
Yes, user session is completely independent of the access token's lifetime. No problem there.
I have already successfully implemented a check for token expiration and a call to /token to retrieve new tokens, however I have been unable to trigger the CookieAuthentication middleware to set a new cookie with the new access token.
This SO question describes the problem better than I did: How do I update my cookie, having got a new access_token?.
The answer suggested this:
SecurityTokenValidated = context =>
{
context.AuthenticationTicket.Properties.AllowRefresh = true;
context.AuthenticationTicket.Properties.IsPersistent = true;
}
And this:
HttpContext.Current.GetOwinContext().Authentication.SignIn(new AuthenticationProperties
{
ExpiresUtc = DateTimeOffset.UtcNow.AddSeconds(tokenResponse.ExpiresIn),
AllowRefresh = true,
IssuedUtc = DateTime.UtcNow,
IsPersistent = true
}, newIdentity);
On every request and within custom Owin middleware, I do this:
if (CheckAccessToken(context))
{
await RefreshTokens(context);
}
And this:
private bool CheckAccessToken(IOwinContext context)
{
var id = (ClaimsIdentity)context.Authentication.User.Identity;
var accessTokenString = id.FindFirst(ClaimTypeKey.AccessToken)?.Value;
if (accessTokenString == null)
return false;
var accessToken = new JwtSecurityTokenHandler().ReadToken(accessTokenString);
var oneMinuteBeforeExpiration = accessToken.ValidTo.AddMinutes(-1); // todo: this should be configurab
var needsRefreshed = oneMinuteBeforeExpiration <= DateTime.UtcNow;
return needsRefreshed;
}
private async Task RefreshTokens(IOwinContext context)
{
var id = (ClaimsIdentity)context.Authentication.User.Identity;
var refreshToken = id.FindFirst(ClaimTypeKey.RefreshToken)?.Value;
if (refreshToken != null)
{
var tokenManager = new TokenManager(_options.Issuer, _options.ClientId, _options.ClientSecret);
var tokenResponse = await tokenManager.RequestNewTokens(refreshToken);
// todo: add exception handling
var newAccessToken = new JwtSecurityTokenHandler().ReadToken(tokenResponse.access_token);
var result = from claim in id.Claims
where claim.Type != ClaimTypeKey.AccessToken
&& claim.Type != ClaimTypeKey.RefreshToken
&& claim.Type != ClaimTypeKey.IdToken
select claim;
var claims = result.ToList();
claims.AddRange(new Claim[]
{
new Claim(ClaimTypeKey.AccessToken, tokenResponse.access_token),
new Claim(ClaimTypeKey.IdToken, tokenResponse.id_token),
new Claim(ClaimTypeKey.RefreshToken, tokenResponse.refresh_token),
});
var newIdentity = new ClaimsIdentity(claims, _options.AuthenticationType);
DateTimeOffset expiresUtc = DateTimeOffset.UtcNow.AddSeconds(tokenResponse.expires_in);
context.Authentication.SignIn(new Microsoft.Owin.Security.AuthenticationProperties()
{
ExpiresUtc = expiresUtc,
AllowRefresh = true,
IssuedUtc = DateTime.UtcNow,
IsPersistent = true
}, newIdentity);
}
}
But this still doesn't trigger the CookieAuthentication middleware to set a new cookie, so subsequent request still contain the old Access Token. :-(
Mark
Thanks for all the details Mark!
Just to clarify, when you say that a new cookie is not being generated, do you mean that context.Authentication.User.Identity
still has the old tokens? In other words, context.Authentication.User.Identity
is not being overwritten with the newIdentity
. Is the ExpiresUtc
being updated?
Have you tried Challenge instead of SignIn
? You can do a quick test to see if the ExpiresUtc
property is being updated.
HttpContext.GetOwinContext().Authentication.Challenge(new AuthenticationProperties()
{
IsPersistent = true,
ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes(5),
...
})
Can you also post your Startup
class configuration, please? That will help us to see the whole project configuration :).
Hi @laura-rodriguez,
Correct. After passing newIdentity
to the SignIn()
method, the middleware does not set a new cookie so it contains all of the old claims and tokens. So, on the next request, the middleware parses the old cookie and restores all of the old claims and tokens.
The Challenge()
method does not accept a ClaimsIdentity
argument like the SignIn()
method does, however....
I tried Challenge()
a few weeks ago and included the refresh_token in the dictionary similar to how we set a sessionToken property after the call to /authn, and this DOES trigger a browser redirect to Okta which obtains new tokens! At first, I thought that this was going to work, but unfortunately this only works if there is already an active Okta session cookie. If I close and reopen my browser (which clears the Okta session cookie), Okta redirects the browser to an Okta sign in page instead of my app's custom login page, does not use the refresh token. :-(
Below is my Startup.Configuration(). You may notice that .UseCeAuthMvc()
looks a lot like Okta's .UseOktaMvc()
. That is no coincidence. It was suggested by Okta's Chris Gustafson that I implement using built-in Microsoft middleware so that I could customize where I need to for our use-case. Most of it is the same, but I did customize in a few places.
public void Configuration(IAppBuilder app)
{
app.UseDebugMiddleware(new DebugMiddlewareOptions
{
OnIncomingRequest = (ctx) =>
{
var watch = new Stopwatch();
watch.Start();
ctx.Environment["DebugStopwatch"] = watch;
},
OnOutgoingRequest = (ctx) =>
{
var watch = (Stopwatch)ctx.Environment["DebugStopwatch"];
watch.Stop();
Debug.WriteLine("Request took: " + watch.ElapsedMilliseconds);
}
});
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
//CookieName = ".AspNet.Cookies", // this is the default
LoginPath = new PathString("/Account/Login"),
// This is critical! See https://github.com/aspnet/AspNetKatana/wiki/System.Web-response-cookie-integration-issues
CookieManager = new SystemWebCookieManager(),
//CookieSameSite = Microsoft.Owin.SameSiteMode.Strict, // Cannot be set to Strict!
// Note: OpenIdConnectAuthenticationOptions.UseTokenLifetime must be false for ExpireTimeSpan and SlidingExpiration to be honored
ExpireTimeSpan = Settings.RememberMeTimeSpan,
SlidingExpiration = true,
Provider = new CeCookieAuthenticationProvider(),
});
//var issuerUrl = CeAuthUrlHelper.CreateIssuerUrl(Settings.OktaDomain, Settings.AuthorizationServerId);
//app.UseRefreshToken(new RefreshTokenMiddlewareOptions(issuerUrl, Settings.ClientId, Settings.ClientSecret));
app.UseCeAuthMvc(new CeAuthOptions
{
Domain = Settings.OktaDomain,
AuthorizationServerId = Settings.AuthorizationServerId,
ClientId = Settings.ClientId,
ClientSecret = Settings.ClientSecret,
Scope = Settings.Scope,
RedirectUri = Settings.RedirectUri,
PostLogoutRedirectUri = Settings.PostLogoutRedirectUri
});
}
CeCookieAuthenticationProvider
is mostly the default CookieAuthenticationProvider. It overrides ResponseSignIn() so that I can set IsPersistent
and AllowRefresh
= true. It also sets and unsets a couple of application cookies on sign in and sign out.
public static IAppBuilder UseCeAuthMvc(this IAppBuilder app, CeAuthOptions options)
{
if (app == null)
{
throw new ArgumentNullException(nameof(app));
}
new CeAuthOptionsValidator().Validate(options);
// Stop the default behavior of remapping JWT claim names to legacy MS/SOAP claim names
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
var openIdConnectOptions = new OpenIdConnectAuthenticationOptionsBuilder(options).BuildOpenIdConnectAuthenticationOptions();
app.UseRefreshToken(new RefreshTokenMiddlewareOptions(openIdConnectOptions));
app.UseOpenIdConnectAuthentication(openIdConnectOptions);
return app;
}
internal OpenIdConnectAuthenticationOptions BuildOpenIdConnectAuthenticationOptions()
{
var issuerUrl = CeAuthUrlHelper.CreateIssuerUrl(_options.Domain, _options.AuthorizationServerId);
var notificationHandler = new OidcNotificationHandler();
var securityTokenNotificationHandler = new SecurityTokenNotificationHandler(issuerUrl);
var openIdConnectOptions = new OpenIdConnectAuthenticationOptions
{
ClientId = _options.ClientId,
ClientSecret = _options.ClientSecret,
Authority = issuerUrl,
RedirectUri = _options.RedirectUri,
ResponseType = OpenIdConnectResponseType.Code,
RedeemCode = true,
SaveTokens = true,
UseTokenLifetime = false, // Must be false in order to use CookieAuthenticationOptions.ExpireTimeSpan
Scope = string.Join(" ", (_options.Scope?.ToArray()) ?? CeAuthDefaults.Scope),
PostLogoutRedirectUri = _options.PostLogoutRedirectUri,
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
ValidAudience = _options.ClientId,
RequireExpirationTime = true,
RequireSignedTokens = true,
ValidateIssuer = true,
ValidIssuer = issuerUrl,
ValidateAudience = true,
ValidateIssuerSigningKey = true,
ValidateLifetime = true,
ClockSkew = TimeSpan.FromMinutes(2),
},
SecurityTokenValidator = new StrictSecurityTokenValidator(),
AuthenticationMode = AuthenticationMode.Passive,
Notifications = new OpenIdConnectAuthenticationNotifications()
{
AuthenticationFailed = notificationHandler.OnAuthenticationFailed,
AuthorizationCodeReceived = notificationHandler.OnAuthorizationCodeReceived,
MessageReceived = notificationHandler.OnMessageReceived,
RedirectToIdentityProvider = notificationHandler.BeforeRedirectToIdentityProviderAsync,
SecurityTokenReceived = securityTokenNotificationHandler.OnSecurityTokenReceived,
SecurityTokenValidated = securityTokenNotificationHandler.OnSecurityTokenValidatedAsync,
TokenResponseReceived = notificationHandler.OnTokenResponseReceived,
}
};
return openIdConnectOptions;
}
public async Task OnSecurityTokenValidatedAsync(SecurityTokenValidatedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> context)
{
//context.AuthenticationTicket.Properties.AllowRefresh = true;
//context.AuthenticationTicket.Properties.IsPersistent = true;
context.AuthenticationTicket.Identity.AddClaim(new Claim(ClaimTypeKey.IdToken, context.ProtocolMessage.IdToken));
context.AuthenticationTicket.Identity.AddClaim(new Claim(ClaimTypeKey.AccessToken, context.ProtocolMessage.AccessToken));
if (!string.IsNullOrEmpty(context.ProtocolMessage.RefreshToken))
context.AuthenticationTicket.Identity.AddClaim(new Claim(ClaimTypeKey.RefreshToken, context.ProtocolMessage.RefreshToken));
FillNameIdentifierClaimOnIdentity(context.AuthenticationTicket.Identity);
if (_getClaimsFromUserInfoEndpoint)
{
await _userInformationProvider.EnrichIdentityViaUserInfoAsync(context.AuthenticationTicket.Identity, context.ProtocolMessage.AccessToken).ConfigureAwait(false);
}
return;
}
private void FillNameIdentifierClaimOnIdentity(ClaimsIdentity identity)
{
var currentNameIdentifier = identity.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value;
var sub = identity.Claims.FirstOrDefault(c => c.Type == "sub")?.Value;
if (currentNameIdentifier == null && sub != null)
{
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, sub));
}
}
Thanks Mark!
I want to keep exploring the Challenge
scenario a bit more to understand why it didn't work, so let me ask you some follow-up questions.
You mentioned that when you tried Challenge
before, the problem you had was that the users were redirected to the Okta login page instead of yours. This usually happens when your OIDC AuthorizationMode
is not set to AuthenticationMode.Passive
. Did you have this set when trying that? You also need to configure the Cookie middleware with your application login path.
There's also a prompt
parameter you can pass that might help with Okta sign-in redirect issue. Have you also explored this?
Sorry if I keep asking more questions, I just want to make sure what scenarios you tried and why they didn't work.
Hi Laura,
I really appreciate your questions! Your questions will help us figure this out together. I love it! š
I have always had AuthenticationMode = AuthenticationMode.Passive
. My cookie authentication middleware has always been set to LoginPath = new PathString("/Account/Login")
, which is what I want.
Thought: I suspect that the refresh_token
challenge without the Okta session cookie was rejected by Okta because it didn't contain everything that was required. During the normal flow, the challenge only contains a sesstionToken
and a RedirectUri
. But during a request when the "access token" is almost (or actually) expired, all I have is a refresh, id, and access token (which may or may not be expired at this point). What would a valid challenge with these things look like? š¤·āāļø
I agree... A "challenge" with a refresh token makes sense. I hope it's that simple. I hope that I am just missing something required with something I have.
Mark
Hi @Good-man ,
Yes, you are right, when you are using a custom login page you need to pass the session id as a parameter.
As far as I can see, the issue here is not with the SDK because you are able to retrieve the tokens, but with Owin and what is the recommended way to update the cookie once you have the updated tokens.
I think this ASP.NET Core sample describes what you are trying to do. I was looking into the equivalent way in ASP.NET, and I think what you can try is moving your token logic into the ValidateIdentity
method in your CeCookieAuthenticationProvider
.
I did a quick test trying to overwrite the tokens, and it seems to work:
/// <summary>
/// Implements the interface method by invoking the related delegate method
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public virtual Task ValidateIdentity(CookieValidateIdentityContext context)
{
var identity = (ClaimsIdentity)context.Identity;
var accessTokenClaim = identity.FindFirst("access_token");
var refreshTokenClaim = identity.FindFirst("refresh_token");
// everything went right, remove old tokens and add new ones
identity.RemoveClaim(accessTokenClaim);
identity.RemoveClaim(refreshTokenClaim);
identity.AddClaims(new[]
{
new Claim("access_token", "foo"),
new Claim("refresh_token", "bar")
});
context.ReplaceIdentity(identity);
return Task.FromResult<object>(null);
}
Let me know if this helps.
Hi @laura-rodriguez ,
This makes sense, but it does not cause the cookie middleware to set a new cookie. So, on the next request the browser passes the old cookie, claims, and access token which is still expired. :-(
Mark
@laura-rodriguez the ResponseSignIn method allows changing the claims before they are converted into a cookie, but it only happens during SignIn. Not on each request.
/// <summary>
/// Called when an endpoint has provided sign in information before it is converted into a cookie.
/// By implementing this method, the claims and extra information that go into the ticket may be altered.
/// </summary>
/// <param name="context"></param>
public override void ResponseSignIn(CookieResponseSignInContext context)
Hi @laura-rodriguez ,
I think that I may have just gotten it to work. I removed the code from the ValidateIdentity
method. I modified the code in my RefreshTokenMiddleware
to remove and add the new claims (instead of creating a new ClaimsIdentity
like I was doing before), then called the .SignIn()
method again... which triggered the .ResponseSignIn()
method to execute in the cookie middleware. At this point, the middleware has the new tokens before the information is converted into a cookie!!
I'm still testing but this is very encouraging!
Mark
Hi @Good-man,
I'm crossing fingers š¤ ! Please, let me know how it goes.
Hi @laura-rodriguez, (cc @chrismorris-okta)
So far, in all my testing, it is working perfectly! š
I believe that this could be added to the Okta ASP.net SDK fairly easily and it would be amazing! Is there anything that I can do to help with this? A pull request?
Mark
Hi @Good-man,
That's awesome! š
We definitely want to know what features you think could be added to the SDK to make the experience better. So, if you have a minimal working project that you can share with us, we can definitely take a look at the bits that can be added to both ASP.NET and ASP.NET Core SDKs. If you already have an idea, feel free to share your thoughts and/or submit a PR ā¤ļø .
We try to make both SDKs consistent in terms of features that they support, so anything we decide to add to ASP.NET we will also spend some time adding it to ASP.NET Core.
@Good-man can you share the refresh token implementation full working code.
@naveenkv66
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
//CookieName = ".AspNet.Cookies", // this is the default
LoginPath = new PathString("/Account/Login"),
// This is critical! See https://github.com/aspnet/AspNetKatana/wiki/System.Web-response-cookie-integration-issues
CookieManager = new SystemWebCookieManager(),
CookieSameSite = SameSiteMode.Lax, // Cannot be set to Strict!
// Note: OpenIdConnectAuthenticationOptions.UseTokenLifetime must be false for ExpireTimeSpan and SlidingExpiration to be honored
// ExpireTimeSpan = TimeSpan, // the cookie won't persist unless IsPersistent is set during SignIn
SlidingExpiration = true,
Provider = new CookieAuthenticationProvider()
{
OnResponseSignIn = (context) =>
{
// placeholder for additional sign in code
},
OnResponseSignOut = (context) =>
{
SensitiveResourceCookie.Expire(context.Response.Cookies);
RememberMeCookie.Expire(context.Response.Cookies);
}
}
});
var scope = OktaSettings.Scope ?? CeAuthDefaults.Scope;
app.UseRefreshToken(new RefreshTokenMiddlewareOptions()
{
Domain = OktaSettings.Domain,
TokenPath = "/v1/token",
IntrospectPath = "/v1/introspect",
AuthorizationServerId = OktaSettings.AuthorizationServerId,
ClientId = OktaSettings.ClientId,
ClientSecret = OktaSettings.ClientSecret,
Scope = scope
});
app.UseCeAuthMvc(new CeAuthMvcOptions
{
Domain = OktaSettings.Domain,
AuthorizationServerId = OktaSettings.AuthorizationServerId,
ClientId = OktaSettings.ClientId,
ClientSecret = OktaSettings.ClientSecret,
IntrospectPath = "/v1/introspect",
Scope = scope,
RedirectUri = OktaSettings.RedirectUri,
UsePkce = true,
PostLogoutRedirectUri = OktaSettings.PostLogoutRedirectUri,
UserInformationProvider = new OktaUserInformationProvider(OktaSettings.Domain, OktaSettings.AuthorizationServerId),
AuthenticationFailed = (context) =>
{
// this is an example of how to handle authentication failed events
var url = new UrlHelper(System.Web.HttpContext.Current.Request.RequestContext)
.Action("AuthenticationFailed", "Error", new { message = context.ProtocolMessage.ErrorDescription });
context.Response.Redirect(url);
context.HandleResponse();
return Task.FromResult(0);
},
RedirectToIdentityProvider = n =>
{
// placeholder for idp redirect hooks
return Task.CompletedTask;
},
AuthorizationCodeReceived = n =>
{
// placeholder for authorization code received hooks
return Task.CompletedTask;
}
});
}
}
public class RefreshTokenMiddleware : OwinMiddleware
{
private readonly RefreshTokenMiddlewareOptions _options;
public RefreshTokenMiddleware(OwinMiddleware next, RefreshTokenMiddlewareOptions options) : base(next)
{
_options = options;
}
public override async Task Invoke(IOwinContext context)
{
if (AccessTokenExpired(context) && HasRefreshToken(context))
{
// TODO: find a way to avoid refreshing tokens when signing out
await RefreshTokens(context);
}
await Next.Invoke(context);
}
private bool HasRefreshToken(IOwinContext context)
{
if (context.Authentication.User.Identity is ClaimsIdentity id && id.IsAuthenticated)
return id.Claims.Any(c => c.Type == ClaimTypeKey.RefreshToken);
return false;
}
private bool AccessTokenExpired(IOwinContext context)
{
if (context.Authentication.User.Identity is ClaimsIdentity id && id.IsAuthenticated)
{
var accessTokenString = id.FindFirst(ClaimTypeKey.AccessToken)?.Value;
if (accessTokenString == null)
return false;
var accessToken = new StrictTokenHandler().ReadToken(accessTokenString);
if (accessToken.ValidTo <= DateTime.UtcNow)
return true;
return false;
}
return false;
}
private async Task RefreshTokens(IOwinContext context)
{
var identity = (ClaimsIdentity)context.Authentication.User.Identity;
var refreshTokenClaim = identity.FindFirst(ClaimTypeKey.RefreshToken);
if (refreshTokenClaim != null)
{
var accessTokenClaim = identity.FindFirst(ClaimTypeKey.AccessToken);
var idTokenClaim = identity.FindFirst(ClaimTypeKey.IdToken);
identity.RemoveClaim(refreshTokenClaim);
identity.TryRemoveClaim(accessTokenClaim);
identity.TryRemoveClaim(idTokenClaim);
var client = new TokenHandler(_options.ClientId, _options.ClientSecret)
{
IntrospectAddress = CeAuthUrlHelper.CreateTokenUrl(_options.Domain, _options.AuthorizationServerId, _options.TokenPath),
};
var tokenResponse = await client.RequestRefreshToken(refreshTokenClaim.Value);
if (tokenResponse.IsError) throw new TokenException(tokenResponse.Error);
identity.AddClaims(new[] {
new Claim(ClaimTypeKey.AccessToken, tokenResponse.AccessToken),
new Claim(ClaimTypeKey.IdToken, tokenResponse.IdentityToken),
new Claim(ClaimTypeKey.RefreshToken, tokenResponse.RefreshToken),
});
var persist = RememberMeCookie.Exists(context.Request.Cookies);
var address = CeAuthUrlHelper.CreateIntrospectUrl(_options.Domain, _options.AuthorizationServerId, _options.IntrospectPath);
var clientId = _options.ClientId;
var clientSecret = _options.ClientSecret;
var introspectUtil = new TokenHandler(clientId, clientSecret) { IntrospectAddress = address };
var expiresUtc = TokenHandler.GetExpiration(await introspectUtil.Introspect(tokenResponse.RefreshToken, "refresh_token"));
context.Authentication.SignIn(new AuthenticationProperties
{
ExpiresUtc = expiresUtc,
IsPersistent = persist,
AllowRefresh = persist,
}, identity);
}
}
}
I am trying the code in the post but always get tokenResponse.IsError to be true.
var tokenClient = new TokenClient(authority + "/v1/token", clientId, clientSecret); var tokenResponse = await tokenClient.RequestAuthorizationCodeAsync(n.Code, authority); if (tokenResponse.IsError) { throw new Exception(tokenResponse.Error); }
The error is
{"error":"invalid_grant","error_description":"The 'redirect_uri' does not match the redirection URI used in the authorization request."}
I have thoroughly checked redirect_uri is correctly and is registered.
I don't understand why any ideas?
Make sure that the grant type you're using is enabled in Okta.
On Mon, Apr 26, 2021, 6:23 AM mrtariqmahmood @.***> wrote:
I am trying the code in the post but always get tokenResponse.IsError to be true.
var tokenClient = new TokenClient(authority + "/v1/token", clientId, clientSecret); var tokenResponse = await tokenClient.RequestAuthorizationCodeAsync(n.Code, authority); if (tokenResponse.IsError) { throw new Exception(tokenResponse.Error); }
The error is
{"error":"invalid_grant","error_description":"The 'redirect_uri' does not match the redirection URI used in the authorization request."}
I have thoroughly checked redirect_uri is correctly and is registered.
I don't understand why any ideas?
ā You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/okta/okta-aspnet/issues/130#issuecomment-826714490, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABCEG3OZLLQJZR25LTDTU63TKU5LPANCNFSM4OWRPDMQ .
Hi @chrismorris-okta
User story
As a website developer, I would like the Okta ASP.net SDK to support the flow of exchanging a refresh token for a new access token.
Our use-case is a feature that we call "keep me logged in" where our customers can continue to access authenticated, but non-sensitive resources when they return to the site for weeks or months (similar to Amazon and Facebook). Accessing sensitive resources, like personal information, will be handled by the application and will challenge the user to reauthenticate.
Proposed solution
The application developer will store the user's refresh and id token somewhere, perhaps a cookie, and trigger a OIDC challenge containing these as authentication properties. The Okta SDK middleware would then issue a call to the /token (instead of the /authorize) endpoint to request a new access token. If successful, the middle ware will set the user to authenticated and continue.
Alternatives considered
An alternative is to add an entirely separate layer to the request pipeline that does all of this work and bipasses the OIDC authentication layer.
Additional information
We are using classic ASP.net MVC with Owin. Core is not an option because our CMS doesn't currently support it.