Handle 404 exception in ASP.Net web API - c#

I have ASP.Net web API in .net framework 4.8.
I have implemented single sign on using Azure active directory bearer token.
User will be redirected to login page if route is found in routing table. However, if user enters incorrect route (https://mysite/invalidurl) then it directly shows a yellow death screen with 404.
Is it possible to redirect user to login page first incase of invalid url (404) also? If yes then how? It means user enters any url (valid or invalid), s/he should be redirected to login page first.
I have already tried few options but none of the option worked for me like ExceptionHandler, AttributeRouting, CustomError in Web.Config, Application_Error in global.asax, global pattern matching in route table ( like this: routeTemplate: "api/{*uri}",)
Here is the code of my startup file
public void ConfigureAuth(IAppBuilder app)
{
List<string> audiences = ConfigurationManager.AppSettings["ida:Audience"].Split(AudienceDelimitter).ToList();
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
AuthenticationType = "OAuth2Bearer",
Tenant = ConfigurationManager.AppSettings["ida:TenantId"],
TokenValidationParameters = new TokenValidationParameters
{
ValidAudiences = audiences
}
});
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = authority,
PostLogoutRedirectUri = postLogoutRedirectUri,
RedirectUri = postLogoutRedirectUri
});
}
Thanks in advance.

Related

How do I get id_token for keycloak using OWIN?

I'm working with an very old VB.net application trying to layer in SSO auth using OWIN and KeyCloak. This is all new to me. The approach I'm taking is to create a C# app to sit in between KeyCloak and my VB app. I've been able to get my C# app to open the login screen of KeyCloak, authenticate and return to the C# app or even the VB app. This seems fine.
However, I need the id_token and username to pass to the VB app. When using Fiddler I can see KeyCloak is generating a post back to my return page with the id_token in tow. However, it is on another thread and gets redirected to the original page but without the id_token. I must be missing something. I've seen code where there are notifications wired and I think they should grab the token and user info, but I don't know how to get the notifications to work. There is no explicit documentation to tell me what to do.
Am I supposed to have a listener or callback method to catch the post from KeyCloak? If so can some one show me how to create one?
Note: I've found some Microsoft code using OWIN and Azure and MVC that bring back user info. However, I point this same code to KeyCloak it authenticates but no user info is returned.
Any help will be greatly appreciated.
-Thanks
In my Startup.cs file I have the following (I've tried many different variations to no avail):
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(
CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
// Sets the ClientId, authority, RedirectUri as obtained from web.config
ClientId = _clientId,
ClientSecret = _clientSecret,
RequireHttpsMetadata = false,
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 id_token - which contains basic information about the signed-in user
ResponseType = OpenIdConnectResponseType.IdToken,
// ValidateIssuer set to false to allow personal and work accounts from any organization to sign in to your application
// To only allow users from a single organizations, set ValidateIssuer to true and 'tenant' setting in web.config to the tenant name
// To allow users from only a list of specific organizations, set ValidateIssuer to true and use ValidIssuers parameter
TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = true
},
// OpenIdConnectAuthenticationNotifications configures OWIN to send notification of failed authentications to OnAuthenticationFailed method
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = OnAuthenticationFailed,
SecurityTokenReceived = OnSecurityTokenReceived
}
}
);
}

OpenIdConnect redirects as if the user wasn't logged in

I'm working on a MVC Project for a customer (let's call this "Project A") and we have to use OpenIdConnect for authorization which I basically got it working, I will be redirected to the login site if i'm not authorized and so on. But if i want to go to an Action with [Authorize], it makes a request to the OpenIdConnect-Authority to see if i'm logged in and then redirects me to the RedirectUrl which is specified for this Client.
So if I want to go to /Home/Contact (in my example with [Authorize]) I can't get there, because i will always be redirected to my "RedirectUrl".
Do I have to store the Requested Url by myself and then redirect myself?
It doesn't matter if I set the RedirectUrl or not. I think it's a bit strange, because in another Project B we go against our Identity Server 4 and there it works without any problems.
This is the code in my Startup.Auth.cs:
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = CookieAuthenticationDefaults.AuthenticationType,
});
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
ClientSecret = clientSecret,
Authority = authority,
RedirectUri = redirectUri,
ResponseType = "code",
Scope = "openid profile email",
Configuration = new Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectConfiguration
{
AuthorizationEndpoint = authority + "as/authorization.oauth2"
},
});
I had to set the AuthorizationEndpoint by myself, because it took always a wrong url. The difference here between Project A and Project B is, that the "ResponseType" is set to "code" in Project A. In Project B it is set to "id_token". But i have to set it to "code" because other Response Types are not accepted.
In Project B i also noticed that the ".AspNet.Cookies" is available on my site.
Which is not the case in Project A. Do i have to create it on my own?

Too many OpenID.nonce cookies cause "Bad Request"

I have already gone through links here, here and here which are related to issue I am having.
I have Silverlight application using IdentiServer3 for authentication and I started having this issue just now when I implemented log out functionality. Note that the issue has nothing to do with Silverlight because login and logout functionality is actually implemented on the server side which is a classic ASP.Net Web form. (.NET 4.5.1)
The application never had logout functionality, so user just used to close the browser so we never encountered this issue before. We have now logout.aspx page and Silverlight application have link to this page.
Logout.aspx page
public partial class Logout : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (Request.IsAuthenticated)
{
Session.Clear();
Request.GetOwinContext().Authentication.SignOut();
}
Response.Redirect("/");
}
}
Default.aspx page. This is starting page
public partial class Default : Page
{
protected void Page_Load(object sender, EventArgs e)
{
// Send an OpenID Connect sign-in request.
if (!System.Web.HttpContext.Current.Request.IsAuthenticated)
{
HttpContext.Current.GetOwinContext().Authentication.Challenge(new AuthenticationProperties { RedirectUri = "/" }, OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
}
}
OWIN startup class where OpenID connection is configured
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Cookies",
LoginPath = new Microsoft.Owin.PathString("/Default.aspx")
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
Authority = ConfigurationManager.AppSettings["Authority"],
Scope = "openid profile",
ClientId = ConfigurationManager.AppSettings["ClientId"],
RedirectUri = ConfigurationManager.AppSettings["RedirectUri"],
ResponseType = "id_token",
SignInAsAuthenticationType = "Cookies",
Notifications = new OpenIdConnectAuthenticationNotifications
{
SecurityTokenValidated = (context) =>
{
var id = context.AuthenticationTicket.Identity;
// create new identity
var newIdentity = new ClaimsIdentity(id.AuthenticationType);
// we want to keep username and subjectid
var sub = id.FindFirst(ClaimTypes.NameIdentifier);
var username = id.FindFirst("preferred_username");
newIdentity.AddClaim(username);
newIdentity.AddClaim(sub);
// keep the id_token for logout
newIdentity.AddClaim(new Claim("id_token", context.ProtocolMessage.IdToken));
context.AuthenticationTicket = new AuthenticationTicket(
newIdentity,
context.AuthenticationTicket.Properties);
return Task.FromResult(0);
},
RedirectToIdentityProvider = (context) =>
{
if (context.ProtocolMessage.RequestType == OpenIdConnectRequestType.LogoutRequest)
{
var idTokenHint = context.OwinContext.Authentication.User.FindFirst("id_token").Value;
context.ProtocolMessage.IdTokenHint = idTokenHint;
}
return Task.FromResult(0);
},
}
Steps to reproduce the issue:
I type web site URL which redirects me to identityserver3 login
page.
I enter credentials and hit login.
After successful login I
get redirected to the web site and There I click log out.
I get logged
out successfully. Fiddler shows the following calls
https://idsvr.mydomain.com/identity/connect/endsession?id_token_hint=XXXXXXXXXXXXXX
https://idsvr.mydomain.com/identity/logout?id=616dd9a4e4c6a55b0bb27faceb4df8dd
https://idsvr.mydomain.com/identity/connect/endsessioncallback?sid=xxxxxx
I land up on https://idsvr.mydomain.com/identity/logout?id=xxxxxxx page as expected.
Now I close the browser ( this step is important)
Now type web site URL again which redirects me to identity server login page. ( like Step 1)
I enter credentials and hit login.
After successful login, IdentityServer makes POST to the web site and web site creates AuthenticationTicket. However, here HttpContext.Current.Request.IsAuthenticated is false so Default.aspx page redirects to IdentityServer. I am already logged in so Indetityserver redirects to client web site and loops continue.
Fiddler shows several round trips from identityServer to web site’s default.aspx page. Each roundtrip keeps adding OpenIdConnect.nonce.OpenIdConnect cookie and ultimately i get bad request error because of max request size.
So as suggested in above links I downgraded Microsoft.Owin.Security.OpenIdConnect to 3.0.0 in Client Application.
However, I still get stuck in continuous loop. The only difference is now it does not add new OpenIdConnect.nonce.OpenIdConnect cookie for each round trip. Fiddler shows only one cookie for each round trip. However HttpContext.Current.Request.IsAuthenticated is still false. So I get stuck in continuous loop.
I had a similar issue with my asp.net mvc application . After some Research i found that there is a bug in Microsoft's Owin implementation for System.Web. The one that is being used when running Owin applications on IIS. Which is what probably 99% of us do, if we're using the new Owin-based authentication handling with ASP.NET MVC5.
The bug makes cookies set by Owin mysteriously disappear on some occasions.
This middleware is a fix for that bug. Simple add it before any cookie handling middleware and it will preserve the authentication cookies.
app.UseKentorOwinCookieSaver();
Here is the link in detail
https://github.com/KentorIT/owin-cookie-saver
What solved the problem for me was using AdamDotNet's Custom OpenIdConnectAuthenticationHandler to delete old nonce cookies.
https://stackoverflow.com/a/51671887/4058784

OWIN OpenID Connect Middleware Not Replacing Current User with ClaimsPrincipal

I have an existing MVC5 application I am converting from using AspNetIdentity to utilize ThinkTecture Identity Server 3 v2. The OpenID provider is not the biggest issue I'm having, as it seems to be working great. The security token is validated and I'm handling the SecurityTokenValidated notification in a method in order to get additional user info claims and add system-specific permission claims to the claim set, similar to the code below:
OWIN Middleware
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
Authority = "https://localhost:44300/identity",
Caption = "My Application",
ClientId = "implicitclient",
ClientSecret = Convert.ToBase64String(SHA256.Create().ComputeHash(Encoding.UTF8.GetBytes("secret"))),
RedirectUri = "http://localhost:8080/",
ResponseType = "id_token token",
Scope = "openid profile email roles",
SignInAsAuthenticationType = CookieAuthenticationDefaults.AuthenticationType,
UseTokenLifetime = false,
Notifications = new OpenIdConnectAuthenticationNotifications
{
SecurityTokenValidated = ClaimsTransformer.GenerateUserIdentityAsync
}
});
Claims Transformer
public static async Task GenerateUserIdentityAsync(SecurityTokenValidatedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
{
var identityUser = new ClaimsIdentity(
notification.AuthenticationTicket.Identity.Claims,
notification.AuthenticationTicket.Identity.AuthenticationType,
ClaimTypes.Name,
ClaimTypes.Role);
var userInfoClient = new UserInfoClient(new Uri(notification.Options.Authority + "/connect/userinfo"),
notification.ProtocolMessage.AccessToken);
var userInfo = await userInfoClient.GetAsync();
identityUser.AddClaims(userInfo.Claims.Select(t => new Claim(t.Item1, t.Item2)));
var userName = identityUser.FindFirst("preferred_username").Value;
var user = MembershipProxy.GetUser(userName);
var userId = user.PersonID;
identityUser.AddClaim(new Claim(ClaimTypes.Name, userId.ToString(), ClaimValueTypes.Integer));
// Populate additional claims
notification.AuthenticationTicket = new AuthenticationTicket(identityUser, notification.AuthenticationTicket.Properties);
}
The problem is that the ClaimsIdentity assigned to the authentication ticket at the end of the transformer is never populated in the System.Web pipeline. When the request arrives to my controller during OnAuthenticationChallenge, I inspect the User property to find an anonymous WindowsPrincipal with a similar WindowsIdentity instance assigned to the Identity property, as if the web config's system.web/authentication/#mode attribute were set to None (at least I believe that's the behavior for that mode).
What might cause a failure by the middleware to set the principal for the user, or for it to be replaced during System.Web's processing with an anonymous Windows identity? I haven't been able to track this down.
EDIT: This occurs irrespective of whether the SecurityTokenValidated notification is handled and claims augmented.
EDIT 2: The cause appears to be making use of ASP.NET State Service session state server in my web.config with cookies. The configuration entry is:
<sessionState mode="StateServer" stateConnectionString="tcpip=127.0.0.1:42424" timeout="30" cookieless="UseCookies" />
This looks to be related to a reported issue in Microsoft.Owin.Host.SystemWeb #197 where cookies are not persisted from the OWIN request context into the System.Web pipeline. There are an assortment of workarounds suggested but I'd like someone to authoritatively point me to a properly vetted solution for this problem.
If you were to capture a fiddler trace, do you see the any .AspNet.Cookies.
I suspect the cookies are not being written. You can use a distributed cache and do away with cookies.
I think you need to include cookie authentication middleware because .AspNet.Cookies thing is written by that middleware. This is how you can integrate that middleware
app.UseCookieAuthentication(new CookieAuthenticationOptions());
Note: Please make sure it should be on top of openid connect middleware
for more details about CookieAuthenticationOptions please goto this link https://msdn.microsoft.com/en-us/library/microsoft.owin.security.cookies.cookieauthenticationoptions(v=vs.113).aspx.

WebForms authentication against Azure AD

I have a WebForms site that has been running on an internal server and authenticating users against our internal Active Directory. Due to some new features that we are implementing, this site needs to be moved to an external server and then authentication changed so that it authenticates users against our Office 365 accounts. To this end I have:
Created a new WebForms site (not using MVC)
Set up a new application in Azure.
Modified the Startup.Auth.cs as follows:
public void ConfigureAuth(IAppBuilder app)
{
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
}
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions { ClientId = "MyApplicationGUID", Authority = "https://login.windows.net/MyDomain.com" });
When I go to the default page and click Log On, it takes me to the correct Login page and the button for OpenID is shown. If I click the button, I am taken to the Microsoft Login page where I am able to enter my credentials. However, at that point, I am redirected back to my site's login page where it is still asking for a username/password.
What I would like to have happen is to set the site up so that if a user is not authenticated, they are redirected directly to the Microsoft login page and upon successful login are redirected back to the page they requested originally. Failing this, I would be satisfied with getting the default login page working so that when I click OpenID I'm not redirected back to the login page.
I don't have time to learn MVC at this point and port the whole thing over so going that route is not an option at this time.
I don't know enough about this process, so if my question doesn't make sense or if you need more information, please let me know and I'll be glad to try and find what you need to assist me in this.
Maybe I'm missing something, but I don't see why you need the custom login page or the external signin cookie. A typical Startup.Auth for OIDC/AAD looks something like this:
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = "AppGUID",
Authority = "https://login.windows.net/MyDomain.com",
// After authentication return user to the page they were trying
// to access before being redirected to the Azure AD signin page.
Notifications = new OpenIdConnectAuthenticationNotifications()
{
RedirectToIdentityProvider = (context) =>
{
string currentUrl = context.Request.Scheme + "://" + context.Request.Host + context.Request.Path;
context.ProtocolMessage.RedirectUri = currentUrl;
return Task.FromResult(0);
}
}
});
The cookie auth is just to keep from going to AAD for every single request. All the real work happens in the OpenIdConnectAuthentication.
Here's an example of WebForms, Azure AD, and OpenID Connect:
http://www.cloudidentity.com/blog/2014/07/24/protecting-an-asp-net-webforms-app-with-openid-connect-and-azure-ad/

Categories