need to pass a default emailid in azure active directory authentication - c#

I am doing Azure Active Directory authentication and using openidconnect for authentication. My application has it own login page and I am trying to redirect the user as soon as they type user id in my login page.
But I am unable to pass userid from my login page to azure login page. I am using following code for
calling azure login page and it redirected correctly but I am not able to pass any default login id which should be displayed on the azure login page like "abc#microsoft.com".
HttpContext.GetOwinContext().Authentication.Challenge(new AuthenticationProperties { RedirectUri = "/" });

You need to add a login_hint query string parameter in the redirect to the authorization server. Here is a post which describes this (in the context of Google login, which also uses OpenID Connect):
http://forums.asp.net/t/1999402.aspx?Owin+pass+custom+query+parameters+in+Authentication+Request
In your case, I suggest you try the following:
HttpContext.GetOwinContext().Authentication.Challenge(
new AuthenticationProperties
{
RedirectUri = "/",
Dictionary =
{
{ "login_hint", "abc#microsoft.com" }
}
});

Related

Authentication/Authorization in ASP.NET Core using Enterprise SingleSignon page and Microsoft SignInManager

Presently I am working on an authentication issue in one of my ASP.NET Core(3.0) application.
To give some background, we have to use an Enterprise Single sign on page to authenticate the users. Once the user got authenticated it redirects back to our application along with user name in a HTTP header called "SM_USER". Then using that information we load corresponding Claim information from DB using Microsoft SignInManger. The application is working fine if the user is accessing the root but it couldn't able to access if they are trying to navigate a specific page directly, like http://website/Controller/Index.
I am suspecting that we may have implemented it wrongly so would like to know how should the below scenario to be implemented?
Our users first get authenticated using an enterprise Login Page(Single sign on) then redirected to our application. user information available on HTTP Headers and the corresponding claims information is available in our DB which we need to use for authorization purpose(we wanted to use Microrosoft SignInManger to load them).
I found the issue after researching it throughly so sharing here in case it useful for someone.
we observed whenever a user try to access a page if the user is not authenticated then it is redirecting Login page(Observed that decorating them with [Authorize] attribute is causing this), where We are using this Login page for local development purpose.
so when the user get redirected to login page and if the environment is not development then below code is executed on the Get method, which takes care of signing the user and creating UserPrincipal. Then after that we redirecting to the page the user requested.
if (!_signInManager.IsSignedIn(User))
{
string userName = HttpContext.Request.Headers["SM_USER"].ToString();
if (userName.Length > 0)
{
var user = await _userManager.FindByNameAsync(userName);
if (user != null)
{
var claimsPrincipal = await _signInManager.CreateUserPrincipalAsync(user);
await _signInManager.Context.SignInAsync(IdentityConstants.ApplicationScheme,
claimsPrincipal,
new AuthenticationProperties { IsPersistent = true });
if (string.IsNullOrEmpty(returnUrl)) //returnUrl is a parameter get passed by the system.
{
return RedirectToAction("<Action>", "<Controller>");
}
else
{
return Redirect(returnUrl);
}
}
}
}

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

How to convert bearer token into authentication cookie for MVC app

I have a 3 tier application structure. There is a cordova js application for end-users, an implementation of identityserver3 which serves as the OpenID authority, and an MVC app which will be access through an in-app browser in the cordova application.
The starting entry point for users is the cordova app. They login there via an in-app browser and can then access application features or click a link to open the in-app browser and visit the MVC app.
Our strategy for securing the MVC website was to use bearer token authentication, since we already logged in once from the app and didn't want to prompt the user to login again when they were directed to the MVC app:
app.Map("/account", account =>
{
account.UseIdentityServerBearerTokenAuthentication(new IdentityServer3.AccessTokenValidation.IdentityServerBearerTokenAuthenticationOptions()
{
Authority = "https://localhost:44333/core",
RequiredScopes = new string[] { "scope" },
DelayLoadMetadata = true,
TokenProvider = new QueryStringOAuthBearerProvider(),
ValidationMode = ValidationMode.ValidationEndpoint,
});
}
Since persisting the access_token on the query string is painful, I implemented a custom OAuthBearerAuthenticationProvider:
public class QueryStringOAuthBearerProvider : OAuthBearerAuthenticationProvider
{
private static ILog logger = LogManager.GetLogger(typeof(QueryStringOAuthBearerProvider));
public override Task RequestToken(OAuthRequestTokenContext context)
{
logger.Debug($"Searching for query-string bearer token for authorization on request {context.Request.Path}");
string value = GetAccessTokenFromQueryString(context.Request);
if (!string.IsNullOrEmpty(value))
{
context.Token = value;
//Save the token as a cookie so the URLs doesn't need to continue passing the access_token
SaveAccessTokenToCookie(context.Request, context.Response, value);
}
else
{
//Check for the cookie
value = GetAccessTokenFromCookie(context.Request);
if (!string.IsNullOrEmpty(value))
{
context.Token = value;
}
}
return Task.FromResult<object>(null);
}
[cookie access methods not very interesting]
}
This works, and allows the MVC application to not have to persist the access token into every request, but storing the access token as just a generic cookie seems wrong.
What I'd really like to do instead is use the access token to work with the OpenID endpoint and issue a forms-auth style cookie, which responds to logout. I found that I can add account.UseOpenIdConnectAuthentication(..) but if I authenticate via access_token, the OpenIdConnectAuthentication bits are simply skipped. Any ideas?
You don't -- access tokens are designed to be used to call web apis. You use the id_token from OIDC to authenticate the user and from the claims inside you issue your local authentication cookie. The Microsoft OpenIdConnect authentication middleware will do most of this heavy lifting for you.

Client Server Facebook Registration / log in flow

I have a problem implementing a Facebook Login on my ASP.NET website. I have both .NET Facebook SDK and the Javascript SDK installed in my site. I can't understand the flow that I need to implement in order to make it work.
In my app, I just need the email to register the user, give him the option to choose a username, and be able to login if he clicks the Facebook log in button (a custom one, it's not the official Facebook log in button).
I prefer a seamless login in which I get login the user via Ajax request to the server and set a cookie on the server instead of a redirect.
What is the registration and login flow that I need to implement to make it work using Ajax?
What I tried to do is to get the token on the client, but when I get the Token, I can't use it on the server, I need a code in order to get an active Token.
On my site I have both a custom Facebook signup and Facebook login buttons.
In my database I have the following columns for Facebook:
facebook_id
token
token_expiration
Here's the client code that I get the accessToken, userId and use it to get the user email:
function fb_login() {
FB.login(function (response) {
if (response.authResponse) {
access_token = response.authResponse.accessToken; //get access token
user_id = response.authResponse.userID; //get FB UID
FB.api('/me', function (response) {
user_email = response.email; //get user email
});
} else {
//user hit cancel button
console.log('User cancelled login or did not fully authorize.');
}
}, {
scope: 'email'
});
}

Trouble with antiforgery token using owin

I'm setting OWIN authentication with my app but I have one problem. I set the validate and anti-forgery token on my login page and when I make a POST all works fine, but on another page setting the validate and anti-forgery token when I make a POST causes the following error to appear:
Anti-forgery token provider was designed for user "user#domain.com" but the current user is "" ...
So how I can fix this without using AntiForgeryConfig.SuppressIdentityHeuristicCheck in Global.asax?
Why doesn't the anti-forgery token take the current user?
UPDATE
The Task<ClaimsIdentity> implements like this:
public override Task<ClaimsIdentity> CreateIdentityAsync(AppUser user, string authenticationType)
{
return Task<ClaimsIdentity>.Factory.StartNew(() =>
{
var claimsList = new List<Claim>()
{
new Claim(ClaimTypes.Name, user.Email),
};
return new ClaimsIdentity(authenticationType);
});
}
And the Task SignInAsync like this:
public override async Task SignInAsync(AppUser user, bool isPersistent, bool rememberBrowser)
{
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
var identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
identity.AddClaim(new Claim(ClaimTypes.Name, user.Email));
AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity);
}
The user.Email is user#domain.com
If the user is logged on then anti forgery token will be issued for that user. Let me give you an example:
Say you have two pages with anti forgery token that submit with form data: Login Page and Search Page
Open search page and perform a search, it will be fine.
Now open login page on another tab (in browser) and login successfully.
Now go back to search page that is already open in the previous tab. Try to perform a search and boooom, you will get anti forgery token error.
Now re-fresh the search page and do another search, it will be fine.
It is because initially the token on search page didn't have user info but the server expects it because the user is logged on. On re-fresh of the search page token gets updated with logged on user info, so it throws no error.

Categories