I'm building a authentication app using OWIN. I'm trying to get both the Bear token and userinfo claims. The code below gets me to 85% of what I want. While initially writing the code I used IIS Express. I debugged and coded towards that environment. For whatever reason after the initial challenge called in the else block the request.isauthenticated is false after the return from the login screen (Using KeyCloak as idp). The code then drops the user into the else if block where I find request.form has my Bearer token. I must then execute the authentication.challenge again (no KeyCloak login screen opens) and I return to the top of the page_load and this time the request.isauthenticated is true and I can get the userinfo but the request.form is empty. This is find for me because I can store all the info off somewhere for later use.
Once I got to this point I targeted IIS. Ran the code and got different behavior. The code drops into the else block initially (same as before) I login but upon return from the idp this time the request.isAuthenticated is true. I have the userinfo but not the Bearer token. Any ideas why??
protected void Page_Load(object sender, EventArgs e)
{
if (Request.IsAuthenticated)
{
String str = String.Empty;
var qry = ((System.Security.Claims.ClaimsPrincipal)Request.RequestContext.HttpContext.User).Claims;
if (null != qry)
{
foreach (System.Security.Claims.Claim item in qry)
{
if (item.Type == "preferred_username")
{
str = item.Value;
}
}
}
}else if (!Request.IsAuthenticated && Request.Form.Count > 0)
{
HttpContext.Current.GetOwinContext().Authentication.Challenge(
new AuthenticationProperties { },
OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
else
{
HttpContext.Current.GetOwinContext().Authentication.Challenge(
new AuthenticationProperties { RedirectUri = "/XXXapp locationXXX/" },
OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
}
I've figured it out,. Needed to set the save token flag to true. This allowed the token to be carried along in the request. So, I don't need if else. Now that I got that working I'm changing this section of code. My main issue is it is hard to find complete and current documentation with sample code for my use case. --Thanks
Related
I'm trying to integrate a Microsoft account login into my ASP.NET MVC app, and I have this controller method:
public void SignIn()
{
// HACK - we will be signed into only one account if we are not signed in to MS
if (Request.GetOwinContext().Authentication.User.Identities.Count() <= 1)
{
// Signal OWIN to send an authorization request to Azure
Request.GetOwinContext().Authentication.Challenge(
new AuthenticationProperties { RedirectUri = "http://localhost:31503/MicrosoftCalendar" },
OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
}
What I expect to happen is that I'm prompted to log in to my Microsoft account; instead, what happens is this method runs over and over and over again, doing nothing at all, until I get a "too many redirects" error in my browser. How can I get the Challenge method to actually do something?
I have a class OwinStartup in my web project; I have it set to be the OWIN startup class like so:
[assembly: OwinStartup(typeof(Root.OwinStartup))]
However for some reason my breakpoints inside this startup class never get hit; OWIN is never being initialized... actually, wait a second, it is being initialized, but the event handlers for things like OnAuthorizationCodeReceivedAsync are never being hit...
If I step through the code, after Challenge is called in the SignIn method, I get redirected for some reason to a UserController, which in turn redirects me back to the SignIn method. I wonder why I'm winding up in the UserController?
edit: I need more code? all right, this method in Global.asax.cs executes immediately after the OWIN calls:
protected void MvcApplication_BeginRequest(object sender, EventArgs e)
{
#region Set the context GUID cookie
if (null == Request.Cookies[CookieName.ContextGUID])
{
Response.SetCookie(new System.Web.HttpCookie(CookieName.ContextGUID, Guid.NewGuid().ToString()));
}
#endregion
// check to see whether SSL is required
if (System.Web.Security.FormsAuthentication.RequireSSL)
{
// check where the request is originating from
if (Request.UserHostName != "127.0.0.1" && Request.UserHostName != "localhost")
{
// check if the request is secure
if (!Request.IsSecureConnection)
{
string url = null;
// check for querystring segments
if (!String.IsNullOrEmpty(Request.ServerVariables["QUERY_STRING"]))
{
url = String.Format("https://{0}{1}?{2}",
Request.ServerVariables["SERVER_NAME"],
Request.ServerVariables["SCRIPT_NAME"],
Request.ServerVariables["QUERY_STRING"]);
}
else
{
url = String.Format("https://{0}{1}", Request.ServerVariables["SERVER_NAME"], Request.ServerVariables["SCRIPT_NAME"]);
}
// redirect to the secure url
Response.Redirect(url);
}
}
}
// verify the request
if (null != Request)
{
// NOTE: This is a workaround for the following exception thrown by the ReportViewer control when
// using a non-IE browser:
// Missing URL parameter: IterationId
// See the following reference: https://connect.microsoft.com/VisualStudio/feedback/details/556989/?wa=wsignin1.0
if (Request.Path.EndsWith("Reserved.ReportViewerWebControl.axd") &&
Request.QueryString["ResourceStreamID"] != null &&
Request.QueryString["ResourceStreamID"].ToLower().Contains("blank.gif"))
{
// intercept the request and send to actual valid image path
Response.Redirect(Constant.ImageRoot + "blank.gif");
}
}
}
Not sure if this is what's causing the infinite redirect loop but here it is...
This is maybe a shot in the dark, but it looks like the controller isn't returning anything because it is a void method, try adding a return type, I'm not overly familier with OWIN so you'll have to forgive me there but here is an example of what I'm talking about:
public ActionResult SignIn()
{
// Signal OWIN to send an authorization request to Azure
return Request.GetOwinContext().Authentication.Challenge(new AuthenticationProperties { RedirectUri = "http://localhost:31503/MicrosoftCalendar" },
OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
two small changes which are in the method signature, returning ActionResult and not a void, you may have to do a bit of research here on the class that OWIN actually returns. and second adding the return keyword, note this will not work with the if statement that you have said is a "hack" because you would require two return statements in that scenario
Hope this helps.
I have front app on angular 5 and backend api on c# using identity server.
The problem is that when I click logout button, the token is removed and i am redirected to logout page.
But when I try to refresh main page, I am redirected to microsoftonline.com
authenticated automatically and redirected back to main page
I am missing providing username and password here, and this occurs in chrome incognito.
What I noticed is that if I remove manually the cookie from microsoftonline.com
and repeat the process, this time I will be asked for username and password.
So first I tried to clean all cookies this way but it din't help
foreach (var key in HttpContext.Request.Cookies.Keys)
{
HttpContext.Response.Cookies.Append(key, "", new CookieOptions() { Expires = DateTime.Now.AddDays(-1) });
}
bellow is my accountcontroller logout method and cookie screen
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Logout(LogoutViewModel model)
{
var idp = User?.FindFirst(JwtClaimTypes.IdentityProvider)?.Value;
var subjectId = HttpContext.User.Identity.GetSubjectId();
if (idp != null && idp != IdentityServerConstants.LocalIdentityProvider)
{
if (model.LogoutId == null)
{
model.LogoutId = await interaction.CreateLogoutContextAsync();
}
try
{
await signInManager.SignOutAsync();
}
catch (NotSupportedException)
{
}
}
// set this so UI rendering sees an anonymous user
HttpContext.User = new ClaimsPrincipal(new ClaimsIdentity());
// get context information (client name, post logout redirect URI and iframe for federated signout)
var logout = await interaction.GetLogoutContextAsync(model.LogoutId);
var vm = new LoggedOutViewModel
{
PostLogoutRedirectUri = logout?.PostLogoutRedirectUri,
ClientName = logout?.ClientId,
SignOutIframeUrl = logout?.SignOutIFrameUrl
};
await persistedGrantService.RemoveAllGrantsAsync(subjectId, "angular2client");
return View("LoggedOut", vm);
}
If I understand correctly you are federating to Microsoft from your IdentityServer4 service? If so when you sign out of your identity service you should also give the user the option to sign out of the external provider (if it supports the relevant feature - it'd need to define an end_session_endpoint in the discovery document).
This functionality is supported by the standard OIDC middleware so you should be able to initiate signout by calling SignoutAsync() and passing the name of the scheme for the MS federated sign in.
Another option is to always send prompt=login in your external sign in requests and then check the auth_time claim you get back. That way to you force interactive sign in always and also verify when it happened.
Try cleaning the cookies from the HttpContext itself, using the extension method, provided by Identity Server, like here.
Or try this:
await HttpContext.SignOutAsync(IdentityServerConstants.DefaultCookieAuthenticationScheme);
in your Logout controller method.
3rd option (what I have in one of my test MVC clients is):
public ActionResult Logout()
{
Request.GetOwinContext().Authentication.SignOut();
return Redirect("/");
}
public void SignoutCleanup(string sid)
{
var cp = (ClaimsPrincipal)User;
var sidClaim = cp.FindFirst("sid");
if (sidClaim != null && sidClaim.Value == sid)
{
Request.GetOwinContext().Authentication.SignOut("Cookies");
}
}
Where the Logout method is called on the button click, and the SignoutCleanup is the one that is passed to Identity Server, when registering the client as a Client.BackChannelLogoutUri (or Client.FrontChannelLogoutUri, or both, depending on your scenario).
PS: Now, in general I think that your approach is not right, but I don't know your full case, so I'm not judging you - just giving and advice.
For front-end clients (Angular, Vue, vanilla JS etc..) it is recommended to use the client-side oidc-client-js library. And here is the usage example. As I said - this is just an advice, but if you are in the very beginning of your authentication setup, I would recommend you to have a look.
I have following code in my Home-controller:
public ActionResult MyPage()
{
if (HttpContext.Request.Cookies["User"] == null)
{
//Create cookie
return RedirectToAction("MyPage", "Home");
}
else
{
string User = HttpContext.Request.Cookies["User"].Value;
foreach (var user in Data.MyUsers)
{
if (user.username == User)
{
//Do some stuff
return View("MyPage");
}
}
HttpContext.Request.Cookies.Remove("User");
//HttpContext.Response.Cookies.Remove("User"); works not sorry ;(
return RedirectToAction("MyPage", "Home");
}
}
Problem: The application don’t delete the cookie (why ever) and run in a loop because the application doesn’t find the user in the data. (The problem is not that the application doesn’t find the user, the problem is the cookie-issue)
Question: How I can fix this issue because in another project of me I works fine. I copy and pasted the code from my other project in my new project, but in the new project it will not work. Yes I use the same version of the asp.net framework :)
Hope you can help me… I’m at the end of my knowhow about c#....
Thank you very much.
Use Expire Property, and set negative value to it, this will expires the cookie i.e in your word this will removed from cookies list.
String cookieName = Request.Cookies["User"].Name;
HttpCookie userCookie = new HttpCookie(cookieName);
userCookie.Expires = DateTime.Now.AddDays(-1);
Response.Cookies.Add(userCookie);
You cant delete a cookie on client, but you can do expire
https://msdn.microsoft.com/en-us/library/ms178195.aspx
I'm using the code below from this post to try and create a custom http module :
public class BasicAuthenticationModule: IHttpModule
{
public void Init(HttpApplication application)
{
application.AuthenticateRequest += new EventHandler(Do_Authentication);
}
private void Do_Authentication(object sender, EventArgs e)
{
var request = HttpContext.Current.Request;
string header = request.Headers["HTTP_AUTHORIZATION"];
if(header != null && header.StartsWith("Basic "))
{
// Header is good, let's check username and password
string username = DecodeFromHeader(header, "username");
string password = DecodeFromHeader(header, password);
if(Validate(username, password)
{
// Create a custom IPrincipal object to carry the user's identity
HttpContext.Current.User = new BasicPrincipal(username);
}
else
{
Protect();
}
}
else
{
Protect();
}
}
private void Protect()
{
response.StatusCode = 401;
response.Headers.Add("WWW-Authenticate", "Basic realm=\"Test\"");
response.Write("You must authenticate");
response.End();
}
private void DecodeFromHeader()
{
// Figure this out based on spec
// It's basically base 64 decode and split on the :
throw new NotImplementedException();
}
private bool Validate(string username, string password)
{
return (username == "foo" && pasword == "bar");
}
public void Dispose() {}
public class BasicPrincipal : IPrincipal
{
// Implement simple class to hold the user's identity
}
}
The code works ok at making the 401 error be returned by the server and the login dialog pop up but when the correct login details are entered the login dialog does not go away.
When debugging the code nothing happens when the Ok button on the dialog is clicked, the event isn't triggered and the user details aren't validated, I can't figure out why this isn't working.
Any help or ideas would be great, thanks.
On Microsoft's asp.net website, there is a good example on how to do custom authentication. Ignore the fact that it says it's about WebAPI. The code uses a IHttpModule, so it works with WebForms, IHttpHandler, asmx, WCF, and anything else that runs in IIS. Copying and pasting the code at the end of that page into a new project works for me. Although, I don't recommend setting the thread's CurrentPrincipal to the authenticated user, like the sample does. I prefer to just use the current context's User property.
If your breakpoint in the module isn't getting hit, then it's almost certainly because the http module wasn't registered correctly. The asp.net page I linked above shows how to register the module in your web.config file, so you should start there. You should be able to use Visual Studio's intellisense auto-complete to complete your class name, which helps make sure you typed it right (although there is a chance that Resharper is doing it on my machine, but I think it's just plain Visual Studio).
I have some proof concept code for a HTTP module. The code checks to see if a cookie exists, if so it retrieves a value, if the cookie does not exist it creates it and sets the value.
Once this is done I write to the screen to see what action has been taken (all nice and simple). So on the first request the cookie is created; subsequent requests retrieve the value from the cookie.
When I test this in a normal asp.net web site everything works correctly – yay! However as soon as I transfer it to SharePoint something weird happens, the cookie is never saved - that is the code always branches into creating the cookie and never takes the branch to retrieve the value - regardless of page refreshes or secondary requests.
Heres the code...
public class SwithcMasterPage : IHttpModule
{
public void Dispose()
{
throw new NotImplementedException();
}
public void Init(HttpApplication context)
{
// register handler
context.PreRequestHandlerExecute += new EventHandler(PreRequestHandlerExecute);
}
void PreRequestHandlerExecute(object sender, EventArgs e)
{
string outputText = string.Empty;
HttpCookie cookie = null;
string cookieName = "MPSetting";
cookie = HttpContext.Current.Request.Cookies[cookieName];
if (cookie == null)
{
// cookie doesn't exist, create
HttpCookie ck = new HttpCookie(cookieName);
ck.Value = GetCorrectMasterPage();
ck.Expires = DateTime.Now.AddMinutes(5);
HttpContext.Current.Response.Cookies.Add(ck);
outputText = "storing master page setting in cookie.";
}
else
{
// get the master page from cookie
outputText = "retrieving master page setting from cookie.";
}
HttpContext.Current.Response.Write(outputText + "<br/>");
}
private string GetCorrectMasterPage()
{
// logic goes here to get the correct master page
return "/_catalogs/masterpage/BlackBand.master";
}
This turned out to be the authentication of the web app. To work correctly you must use a FQDM that has been configured for Forms Authentication.
You can use Fiddler or FireBug (on FireFox) to inspect response to see if your cookie is being sent. If not then perhaps you can try your logic in PostRequestHandlerExecute. This is assuming that Sharepoint or some other piece of code is tinkering with response cookies. This way, you can be the last one adding the cookie.