I have a ASP.NET MVC application with framework 4.7.2. The application is configured to use IdentityServer3 using OpenIDConnect. When user clicks on Logout button the following code is invoked
Action Method The logout action method get invoked first.
[HttpPost]
public ActionResult Logout()
{
Session.Clear();
if (Request.IsAuthenticated)
{
Request.GetOwinContext().Authentication.SignOut();
}
return Redirect("/");
}
In Owin Startup.cs i have configured OpenIDConnect. The RedirectToIdentityProvider event fires next.
Here, I am setting IdTokenHint when RequestType is Logout.
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
var cookieOptions = new CookieAuthenticationOptions
{
AuthenticationType = "Cookies",
LoginPath = new Microsoft.Owin.PathString("/Home"),
SlidingExpiration = true,
ExpireTimeSpan = GetCookieExpiration()
};
var openIdOptions = new OpenIdConnectAuthenticationOptions
{
Authority = ConfigurationManager.AppSettings["id:Authority"],
Scope = "openid email profile",
ClientId = "My ClientId",
RedirectUri = "http://localhost:58641/Home",
ResponseType = "id_token",
SignInAsAuthenticationType = "Cookies",
UseTokenLifetime = false,
Notifications = new OpenIdConnectAuthenticationNotifications
{
SecurityTokenValidated = (context) =>
{
//code here removed for brevity
return Task.FromResult(0);
},
RedirectToIdentityProvider = (context) =>
{
if (context.ProtocolMessage.RequestType == Microsoft.IdentityModel.Protocols.OpenIdConnectRequestType.LogoutRequest)
{
var idTokenHint = context.OwinContext.Authentication.User.FindFirst("id_token").Value;
context.ProtocolMessage.IdTokenHint = idTokenHint;
}
return Task.FromResult(0);
}
}
};
app.UseCookieAuthentication(cookieOptions);
app.UseOpenIdConnectAuthentication(openIdOptions);
MvcHandler.DisableMvcResponseHeader = true;
}
I fiddler i see it makes a call to
/identity/connect/endsession?id_token_hint= xxxxxxxx However, the HTTP Verb its using
is OPTIONS. So the IdentityServer throws error The requested resource does not support http method 'OPTIONS'
Not sure what i am missing Here.
Edit 1
In browser console i see the following error
Access to XMLHttpRequest at
'https://localhost:44300/identity/connect/endsession?id_token_hint=xxxxxxx'
(redirected from 'http://localhost:58641/account/logout') from origin
'http://localhost:58641' has been blocked by CORS policy: Response to
preflight request doesn't pass access control check: No
'Access-Control-Allow-Origin' header is present on the requested
resource.
Edit 2
I have another ASP.NET Application that has the same logout code. But its making GET request to endsession.
When you see the use of OPTIONS and the request contains the origin header, then that is a CORS preflight request. This is an extra security request that occurs when a JavaScript client tries to make an AJAX request to an API.
Is this intended to trigger the endsession from JavaScript? if so, you need for that client in identityServer set:
AllowedCorsOrigins =
{
"https://localhost:xxxxx"
},
This is set per client in IdentityServer.
Related
I am working on ASP.Net MVC 4.7.2 (classics). Using Azure AD Authenticaiton (Microsoft Identity platform) for authentication and for Web Api authorization.
When using code flow to get the api resource I wan to store the token and get it silently when needed. I got the code from MSAL team from Git. But code doesn't work. Whenever I acquire the code silently the I get the error. When I debugged the issue I found that IAccounet is return null see the following line that returns null.
IAccount account =
_MsalAppBuilder.GetAccountAsync(ClaimsPrincipal.Current.GetAccountId()).Result;
Since account is null therefore the next line of code throws error. Therefore nt aoo us unable to work
AuthenticationResult result = app.AcquireTokenSilent(scopes, account).ExecuteAsync().Result;
As I debugged the issue, I could not find any reason why it is happening and even after extensive search. However what I found that in the Startup class, the method AcquireAccessToken never hit, thus the token does not save.
Can something help understand it please.
MSAL class:
public static class MsalAppBuilder
{
public static string GetAccountId(this ClaimsPrincipal claimsPrincipal)
{
string oid = claimsPrincipal.GetObjectId();
string tid = claimsPrincipal.GetTenantId();
return $"{oid}.{tid}";
}
private static IConfidentialClientApplication clientapp;
public static IConfidentialClientApplication BuildConfidentialClientApplication()
{
if (clientapp == null)
{
clientapp = ConfidentialClientApplicationBuilder.Create(Globals.clientId)
.WithClientSecret(Globals.clientSecret)
.WithRedirectUri(Globals.redirectUri)
.WithAuthority(new Uri(Globals.authority))
.Build();
// In-memory distributed token cache
clientapp.AddDistributedTokenCache(services =>
{
services.AddDistributedMemoryCache();
services.Configure<MsalDistributedTokenCacheAdapterOptions>(o =>
{
o.Encrypt = true;
});
});
}
return clientapp;
}
//this was commented already
/*
// Could also use other forms of cache, like Redis
// See https://aka.ms/ms-id-web/token-cache-serialization
clientapp.AddDistributedTokenCache(services =>
{
services.AddStackExchangeRedisCache(options =>
{
options.Configuration = "localhost";
options.InstanceName = "SampleInstance";
});
});
*/
public static async Task RemoveAccount()
{
BuildConfidentialClientApplication();
var userAccount = await clientapp.GetAccountAsync(ClaimsPrincipal.Current.GetAccountId());
if (userAccount != null)
{
await clientapp.RemoveAsync(userAccount);
}
}
startup class:
public void Configuration(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
//app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
// CookieManager = new SystemWebCookieManager()
AuthenticationType = "Cookies",
CookieManager = new Microsoft.Owin.Host.SystemWeb.SystemWebChunkingCookieManager()
});
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
// Sets the ClientId, authority, RedirectUri as obtained from web.config
ClientId = clientId,
Authority = authority,
RedirectUri = redirectUri,
// PostLogoutRedirectUri is the page that users will be redirected to after sign-out. In this case, it is using the home page
PostLogoutRedirectUri = redirectUri,
Scope = OpenIdConnectScope.OpenIdProfile,
// ResponseType is set to request the code id_token - which contains basic information about the signed-in user
//ResponseType = OpenIdConnectResponseType.CodeIdToken,
ResponseType = OpenIdConnectResponseType.CodeIdToken,
// OpenIdConnectAuthenticationNotifications configures OWIN to send notification of failed authentications to OnAuthenticationFailed method
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthorizationCodeReceived = OnAuthorizationCodeReceived,
AuthenticationFailed = OnAuthenticationFailed
}
}
);
}
private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification context)
{
var scopes = Globals.scopeTravelAuthApi;
IConfidentialClientApplication clientApp = MsalAppBuilder.BuildConfidentialClientApplication();
AuthenticationResult result = await clientApp.AcquireTokenByAuthorizationCode(new[] { scopes}, context.Code).ExecuteAsync().ConfigureAwait(true);
context.HandleCodeRedemption(result.AccessToken, result.IdToken);
}
/// <summary>
/// Handle failed authentication requests by redirecting the user to the home page with an error in the query string
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
private Task OnAuthenticationFailed(AuthenticationFailedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> context)
{
context.HandleResponse();
context.Response.Redirect("/?errormessage=" + context.Exception.Message);
return Task.FromResult(0);
}
}
By the way, I have checked all the settings related to Azure AD they ar correct. In the same app I am accessing Graph API which is working fine only issue is getting the access token silently.
My project is .ASP.Net 4.8. In order to fix the issue, I did not update the code, my code remain as is. I just upgraded the NuGet Packages whatever that make sence or needed to be upgraded for ASP.Net 4.8 but specifically the following:
Microsoft.Identity.Client, Microsoft.Identity.Client.Extensions.Msal, Microsoft.Identity.Web.TokenCache, and owin Nuget packages.
I'm new to Azure AD B2C. I'm trying to set up azure B2C authentication for an MVC application.
The login works fine locally, but when it's not working on server.
The application is hosted on Azure AD.
I don't know if I missed something!! Can someone please help?
private OpenIdConnectAuthenticationOptions CreateOptionsFromPolicy(string policy)
{
return new OpenIdConnectAuthenticationOptions
{
// set the authentication type to the id of the policy
MetadataAddress = metaDataAddress,
AuthenticationType = policy,
// These are standard OpenID Connect parameters, with values pulled from web.config
ClientId = clientId,
//ClientSecret = clientSecret,
RedirectUri = redirectUri,
PostLogoutRedirectUri = redirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = AuthenticationFailed,
},
Scope = "openid",
ResponseType = "id_token",
// used for displaying the user's name in the navigation bar.
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
RoleClaimType = "role",
SaveSigninToken = true
}
};
}
When it is deployed on to the server, after sign in, it is not returning to the application. Instead the page seems to blink and in between I can see something displayed as "As part of the authentication process the page is displayed several times, please click the button to continue"..
Startup.cs file seems alright to me.Couple things to cross check:
redirectUri matches with the website
Here is my signup and sign in action method:
public void SignIn()
{
if (!Request.IsAuthenticated)
{
HttpContext.GetOwinContext().Authentication.Challenge(
new AuthenticationProperties() { RedirectUri = "/" }, Startup.SignInPolicyId);
}
}
public void SignUp()
{
if (!Request.IsAuthenticated)
{
HttpContext.GetOwinContext().Authentication.Challenge(
new AuthenticationProperties() { RedirectUri = "/" }, Startup.SignUpPolicyId);
}
}
I have followed below github repo and it worked for me. Try this and see if it works
https://github.com/tjoudeh/Azure-Active-Directory-B2C/tree/master/AADB2C.WebClientMvc/Controllers
Reference:
http://bitoftech.net/2016/08/31/integrate-azure-ad-b2c-asp-net-mvc-web-app/
Please provide the detailed error with entire code base will try to reproduce at my end.
I'm making a web application by using Web API 2 and MVC 5.
My app has api :
api/account/login, which is used for checking posted information and throw status 200 when an account is granted to access application.
Also, I have one view : /Home/Index which is only available to authenticated client.
Now, my approach is :
Call api/account/login, receive the cookie thrown from that api.
Attach thrown back cookie to browser.
When user access /Home/Index, view is available for him/her.
My questions are :
- Is my approach possible ?
- How can I encrypt my cookie in Web API 2 like MVC 5 does to its cookie ?
Thank you,
The best way to achieve this to have a authorization server (a webAPI generating a token) and token consumption middle ware in your MVC project.IdentityServer https://github.com/IdentityServer/IdentityServer3 should help. However I have done this as below
Built an authorization server using JWT with WEB API and ASP.Net Identity as explained here http://bitoftech.net/2015/02/16/implement-oauth-json-web-tokens-authentication-in-asp-net-web-api-and-identity-2/
once you do that your webAPIs startup.cs will look like below
/// Configures cookie auth for web apps and JWT for SPA,Mobile apps
private void ConfigureOAuthTokenGeneration(IAppBuilder app)
{
// Configure the db context, user manager and role manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationRoleManager>(ApplicationRoleManager.Create);
//Cookie for old school MVC application
var cookieOptions = new CookieAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
CookieHttpOnly = true, // JavaScript should use the Bearer
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/api/Account/Login"),
CookieName = "AuthCookie"
};
// Plugin the OAuth bearer JSON Web Token tokens generation and Consumption will be here
app.UseCookieAuthentication(new CookieAuthenticationOptions());
OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
//For Dev enviroment only (on production should be AllowInsecureHttp = false)
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/oauth/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(30),
Provider = new CustomOAuthProvider(),
AccessTokenFormat = new CustomJwtFormat(ConfigurationManager.AppSettings["JWTPath"])
};
// OAuth 2.0 Bearer Access Token Generation
app.UseOAuthAuthorizationServer(OAuthServerOptions);
}
You can find CustomOAuthProvider,CustomJwtFormat classes here https://github.com/tjoudeh/AspNetIdentity.WebApi/tree/master/AspNetIdentity.WebApi/Providers
Write a consumption logic (i.e. middleware) in all my other APIs (Resource servers) that you want to secure using same token. Since you want to consume the token generated by webAPI in your MVC project, after implementing Authorization server you need to do below
In your MVC app add below in startup.cs
public void Configuration(IAppBuilder app)
{
ConfigureOAuthTokenConsumption(app);
}
private void ConfigureOAuthTokenConsumption(IAppBuilder app)
{
var issuer = ConfigurationManager.AppSettings["AuthIssuer"];
string audienceid = ConfigurationManager.AppSettings["AudienceId"];
byte[] audiencesecret = TextEncodings.Base64Url.Decode(ConfigurationManager.AppSettings["AudienceSecret"]);
app.UseCookieAuthentication(new CookieAuthenticationOptions { CookieName = "AuthCookie" , AuthenticationType=DefaultAuthenticationTypes.ApplicationCookie });
//// Api controllers with an [Authorize] attribute will be validated with JWT
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Passive,
AuthenticationType = "JWT",
AllowedAudiences = new[] { audienceid },
IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
{
new SymmetricKeyIssuerSecurityTokenProvider(issuer, audiencesecret)
}
});
}
In your MVC controller when you receive the token de-serialize it and generate a cookie from the access token
AccessClaims claimsToken = new AccessClaims();
claimsToken = JsonConvert.DeserializeObject<AccessClaims>(response.Content);
claimsToken.Cookie = response.Cookies[0].Value;
Request.Headers.Add("Authorization", "bearer " + claimsToken.access_token);
var ctx = Request.GetOwinContext();
var authenticateResult = await ctx.Authentication.AuthenticateAsync("JWT");
ctx.Authentication.SignOut("JWT");
var applicationCookieIdentity = new ClaimsIdentity(authenticateResult.Identity.Claims, DefaultAuthenticationTypes.ApplicationCookie);
ctx.Authentication.SignIn(applicationCookieIdentity);
Generate a machine key and add it in web.config of your webAPI and ASP.Net MVC site.
With this a cookie will be created and [Authorize] attribute in MVC Site and WebAPI will honor this cookie.
P.S. - I have done this with a web API issuing JWT (Authorization server or Auth & resource server) and successfully able to consume in a ASP.Net MVC website, SPA Site built in Angular , secure APIs built in python (resource server) , spring (resource server), Android App.
You could set the cookie once the user has authenticated against the Account controller.
public class AccountController
{
public HttpResponseMessage Login()
{
// Your authentication logic
var responseMessage = new HttpResponseMessage();
var cookie = new CookieHeaderValue("session-id", "12345");
cookie.Expires = DateTimeOffset.Now.AddDays(1);
cookie.Domain = Request.RequestUri.Host;
cookie.Path = "/";
responseMessage.Headers.AddCookies(new CookieHeaderValue[] { cookie });
return responseMessage;
}
}
To authenticate you can put the [Authenticate] attribute on your Home controller.
public class HomeController
{
[Authenticate]
public ActionResult Index()
{
return View();
}
}
The Authenticate attribute can also be applied at the Controller level if needed.
[Authenticate]
public class HomeController
{
}
You can also make your own authorization attribute if needed by overriding AuthorizeCore and checking for a valid cookie:
public class CustomAuth : AuthenticationAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
HttpCookie authCookie = httpContext.Request.Cookies["CookieName"];
// Your logic
return true;
}
}
I have a mvc 5 app that uses forms authentication but the real Authentication of user happens using bearer token in web api . I'm adding token details in the cookie so the website is always authenticated. MVC and Web api are in same project. Web api hosted using Owin.
here is my code snippet.
startup.cs
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
//Configure authorization
ConfigureOAuth(app);
//register WebAPI
app.UseWebApi(ConfigureWebApiRoutes());
}
}
startup.auth.cs
// Enable the application to use a cookie to store information for the signed in user
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Login/Login"),
CookieHttpOnly = true,
//AuthenticationMode = AuthenticationMode.Passive,
CookieName = "YetAnotherTodo.WebApi.Auth",
//#if DEBUG
// CookieSecure = CookieSecureOption.Never
//#endif
});
// Use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
OAuthBearerOptions = new OAuthBearerAuthenticationOptions();
// using OAuth authentication server as authentication middle ware and Token Generation
app.UseOAuthAuthorizationServer(new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = new SimpleAuthorizationServerProvider(),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
#if DEBUG
AllowInsecureHttp = true
#endif
});
app.UseOAuthBearerAuthentication(OAuthBearerOptions);
code in MVC Login Controller
[AllowAnonymous]
[HttpPost]
public async Task<ActionResult> Login(LoginViewModel model, string redirectUrl = null)
{
if (!ModelState.IsValid) return View(model);
try
{
if (string.IsNullOrWhiteSpace(redirectUrl))
{
redirectUrl = "~/Home";
}
var result = await WebApiService.Instance.AuthenticateAsync<LogInResult>(model.UserName, model.Password);
//Let's keep the user authenticated in the MVC webapp.
//By using the AccessToken, we can use User.Identity.Name in the MVC controllers to make API calls.
FormsAuthentication.SetAuthCookie(result.AccessToken, model.RememberMe);
//Create an AuthenticationTicket to generate a cookie used to authenticate against Web API.
//But before we can do that, we need a ClaimsIdentity that can be authenticated in Web API.
var claims = new[]
{
new Claim(ClaimTypes.Name, model.UserName),
//Name is the default name claim type, and UserName is the one known also in Web API.
new Claim(ClaimTypes.NameIdentifier, model.UserName)
//If you want to use User.Identity.GetUserId in Web API, you need a NameIdentifier claim.
};
//Generate a new ClaimsIdentity, using the DefaultAuthenticationTypes.ApplicationCookie authenticationType.
//This also matches what we've set up in Web API.
var claimsIdentity = new ClaimsIdentity(claims,DefaultAuthenticationTypes.ApplicationCookie);
var authProperties = new AuthenticationProperties
{
ExpiresUtc = result.Expires,
IsPersistent = model.RememberMe,
IssuedUtc = result.Issued,
RedirectUri = redirectUrl
};
var authTicket = new AuthenticationTicket(claimsIdentity, authProperties);
//And now it's time to generate the cookie data. This is using the same code that is being used by the CookieAuthenticationMiddleware class in OWIN.
byte[] userData = DataSerializers.Ticket.Serialize(authTicket);
//Protect this user data and add the extra properties. These need to be the same as in Web API!
//byte[] protectedData = MachineKey.Protect(userData, new[] { "Microsoft.Owin.Security.Cookies.CookieAuthenticationMiddleware", DefaultAuthenticationTypes.ApplicationCookie, "v1" });
//base64-encode this data.
string protectedText = TextEncodings.Base64Url.Encode(userData);
//And now, we have the cookie.
Response.SetCookie(new HttpCookie("YetAnotherTodo.WebApi.Auth")
{
HttpOnly = true,
Expires = result.Expires.UtcDateTime,
Value = protectedText
});
Code in my provider that generates token
AuthenticationTicket ticket;
var cookiesIdentity = GenerateCookiesIdentity(context, user, out ticket);
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(cookiesIdentity);
I was able to login and able to get ticket from token server but on subsequent request or redirect to home page after logging in , I'm still getting 401 error.
This is kinda combinations of these two blogs/tutorials : Blog 1 and Blog 2
I'm trying to Redirect user to Dashboard but it always redirect it to Home/Index that is because I've set RedirectUri to http://localhost:35641/ in Identity Server Options. But that is true in case of application landing page after login it needs to redirect o dashboard. I can write custom logic in Index's Action Result but I want to avoid it.
MVC web Startup method
public void Configuration(IAppBuilder app)
{
// Implicit mvc owin
JwtSecurityTokenHandler.InboundClaimTypeMap = new Dictionary<string, string>();
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Cookies"
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
ClientId = ApplicationConstants.ClientIdNucleusMvcApp,
Authority = ApplicationConstants.UrlBaseAuth,
RedirectUri = ApplicationConstants.UrlBaseWeb,
PostLogoutRedirectUri = ApplicationConstants.UrlBaseWeb,
ResponseType = "id_token token",
Scope = string.Format("openid email {0}", ApplicationScopes.MvcApp),
SignInAsAuthenticationType = "Cookies",
// sample how to access token on form (when adding the token response type)
Notifications = new OpenIdConnectAuthenticationNotifications
{
SecurityTokenValidated = async n =>
{
// Adding access token in claims
var accessToken = n.ProtocolMessage.AccessToken;
if (!string.IsNullOrEmpty(accessToken))
{
n.AuthenticationTicket.Identity.AddClaim(new Claim("access_token", accessToken));
}
// Adding identity token in claims
var identityToken = n.ProtocolMessage.IdToken;
if (!string.IsNullOrEmpty(identityToken))
{
n.AuthenticationTicket.Identity.AddClaim(new Claim("identity_token", identityToken));
}
},
RedirectToIdentityProvider = async n =>
{
// if signing out, add the id_token_hint
if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.LogoutRequest)
{
var idToken = n.OwinContext.Authentication.User.FindFirst("identity_token");
n.ProtocolMessage.IdTokenHint = idToken == null ? null : idToken.Value;
n.ProtocolMessage.PostLogoutRedirectUri = ApplicationConstants.UrlBaseWeb;
}
}
}
});
}
Here is my Client on Identity Server
new Client
{
Enabled = true,
ClientName = ApplicationConstants.ClientNameNucleusMvcApp,
ClientId = ApplicationConstants.ClientIdNucleusMvcApp,
ClientSecrets = new List<ClientSecret>
{
new ClientSecret(ApplicationConstants.ClientSecretNucleusMvcApp.Sha256())
},
Flow = Flows.Implicit,
RequireConsent = false,
AccessTokenType = AccessTokenType.Reference,
IdentityTokenLifetime = 1800,
AccessTokenLifetime = 1800,
RedirectUris = new List<string>
{
// MVC form post sample
ApplicationConstants.UrlBaseWeb,
ApplicationConstants.UrlBaseWeb + "Dashboard/Index"
},
PostLogoutRedirectUris = new List<string>
{
ApplicationConstants.UrlBaseWeb
}
}
Help will be appreciated. Thanks
The RedirectUri you use for talking with your authority should not make a difference, that's just used for dispatching the token back to your application. After that there is an internal (==local to the app) redirect that is used for setting the session cookie and can go anywhere you want within the site. How do you trigger authentication? If you started from a protected action via [authorize], you should always land back in there in the end. If you are using explicit sign in code like if
HttpContext.GetOwinContext().Authentication.Challenge(new AuthenticationProperties { RedirectUri = "/" }, OpenIdConnectAuthenticationDefaults.AuthenticationType);
you can always specify whatever desired landing route you want in RedirectUri. I know, it is fantastically confusing that the property driving this internal redirect has the exact same name as the protocol counterpart - the only excuse we have is that the AuthenticationProperties class already existed when the new claims based middleware was introduced, and calling the actual OAuth/OIDC redirect_uri with the underscore didn't fly with the .NET community. HTH