I am using the OWIN middleware in an external Authentication Server that my applications authenticate to using OAuth Authorisation Code Grant flow.
I can redirect to the Authentication Server, authenticate against an external provider (Google) and redirect back to my client application with a logged in user and Application Cookie set just fine, however when I try to sign out the cookie remains after I call the AuthenticationManager.SignOut method.
My cookie options in Startup.Auth.cs are:
var cookieOptions = new CookieAuthenticationOptions
{
Provider = cookieProvider,
AuthenticationType = "Application",
AuthenticationMode = AuthenticationMode.Passive,
LoginPath = new PathString("/Account/Index"),
LogoutPath = new PathString("/Account/Logout"),
SlidingExpiration = true,
ExpireTimeSpan = TimeSpan.FromMinutes(30),
};
app.UseCookieAuthentication(cookieOptions);
app.SetDefaultSignInAsAuthenticationType(DefaultAuthenticationTypes.ExternalCookie);
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
My login method:
var loginInfo = await AuthManager.GetExternalLoginInfoAsync();
SignInManager.ExternalSignInAsync(loginInfo, true);
var identity = AuthManager.AuthenticateAsync(DefaultAuthenticationTypes.ExternalCookie).Result.Identity;
if (identity != null)
{
AuthManager.SignIn(
new AuthenticationProperties {IsPersistent = true},
new ClaimsIdentity(identity.Claims, "Application", identity.NameClaimType, identity.RoleClaimType));
var ticket = AuthManager.AuthenticateAsync("Application").Result;
var identity = ticket != null ? ticket.Identity : null;
if (identity == null)
{
AuthManager.Challenge("Application");
return new HttpUnauthorizedResult();
}
identity = new ClaimsIdentity(identity.Claims, "Bearer", identity.NameClaimType, identity.RoleClaimType);
AuthManager.SignIn(identity);
}
return Redirect(Request.QueryString["ReturnUrl"]);
Sign Out method:
var authTypeNames = new List<string>();
authTypeNames.Add("Google");
authTypeNames.Add("Application");
authTypeNames.Add("Bearer");
authTypeNames.Add(DefaultAuthenticationTypes.ExternalCookie);
Request.GetOwinContext().Authentication.SignOut(authTypeNames.ToArray());
I have looked at other questions like:
OWIN authentication, expire current token and remove cookie
and
OWIN - Authentication.SignOut() doesn't remove cookies
with no luck. I'm aware I could manually delete the cookie by setting a negative expiry date, but I'd prefer to use in built method if possible.
How do I get the Application Cookie to be removed when I Sign Out?
In order for the SignOut method to flag the authentication ticket (cookie) for removal from the client, the AuthenticationType parameter you pass into the SignOut method and value on the cookie must match exactly. If you want to remove more than one authentication ticket from the client then you'll have to match ALL of those AuthenticationTypes and pass those as a string[] to the SignOut method.
The AuthenticationType of an authentication ticket usually prefixed with the name of the host web container (i.e. something like ".AspNet.") followed by whatever you bootstrapped your OWIN CookieAuthentication settings with.
It looks like you set your AuthenticationType string value to "Application" in Startup.Auth.cs. Try simply calling:
Request.GetOwinContext().Authentication.SignOut("Application");
If that's not working for you, I would debug your application and take a look at the specific AuthenticationType on the identity for each type of authenticated user your application allows, note the value of the AuthenticationType for each one and try including them all in a string[] in your SignOut call.
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie);
FormsAuthentication.SignOut();
Session.Abandon();
From an other StackOverFlow answer which worked for me: OWIN - Authentication.SignOut() doesn't seem to remove the cookie
Use only one of these:
Request.GetOwinContext().Authentication.SignOut();
Request.GetOwinContext().Authentication.SignOut(Microsoft.AspNet.Identity.DefaultAuthenticationTypes.ApplicationCookie);
HttpContext.Current.GetOwinContext().Authentication.SignOut(Microsoft.AspNet.Identity.DefaultAuthenticationTypes.ApplicationCookie);
https://dzone.com/articles/catching-systemwebowin-cookie
I would assume the second one would work for you, but it looks like that's what you're doing. Can you test that on its own? Comment out your array and confirm that that works or doesn't.
To be honest, I don't know enough about OWIN to know about the Passive Authentication mode, however.
I worked on this for days. Here is what finally worked for me. First thing I do is clear the token cache. Next, I create an array of Auth Application Types. I added these 4. You can add more, if you are using them. To my knowledge, I'm only using Cookies and OpenIdConnect, but I added Bearer and Application to be safe. The final step is to clear all remaining Cookies, if any, and any remaining Sessions, if any. Again, I worked on this for days. It was so frustrating. I'm currently using 4.0.1 of these packages.
Install-Package Microsoft.Owin.Security.OpenIdConnect
Install-Package Microsoft.Owin.Security.Cookies
Install-Package Microsoft.Owin.Host.SystemWeb
public ActionResult SignOut()
{
if (Request.IsAuthenticated)
{
string userId = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value;
if (!string.IsNullOrEmpty(userId))
{
// Get the user's token cache and clear it
SessionTokenCache tokenCache = new SessionTokenCache(userId, HttpContext);
string sessionID = HttpContext.Session.SessionID;
tokenCache.Clear(sessionID);
}
}
var authTypeNames = new List<string>();
authTypeNames.Add("Cookies");
authTypeNames.Add("Application");
authTypeNames.Add("Bearer");
authTypeNames.Add("OpenIdConnect");
// Send a sign-out request.
HttpContext.GetOwinContext().Authentication.SignOut(authTypeNames.ToArray());
Request.Cookies.Clear();
Session.RemoveAll();
return RedirectToAction("Index", "Home");
}
If you have any master page then please add the below tag. May be this would be helpful.
<meta http-equiv="Cache-control" content="no-cache" />
Related
This follows on from a previous post which started as a general issue and is now more specific.
In short, I've been following guidance (such as this from Microsoft, this from Scott Hanselman, and this from Barry Dorrans) to allow me to share the authentication cookie issued by a legacy ASP.NET web app with a new dotnet core app running on the same domain.
I'm confident that I'm using the recommended Microsoft.Owin.Security.Interop library correctly. On that side (the old ASP.NET app), the CookieAuthenticationOptions are configured with AuthenticationType and CookieName both set to the same value - SiteIdentity. This same value is also used in the interop data protector setup:
var appName = "SiteIdentity";
var encryptionSettings = new AuthenticatedEncryptorConfiguration
{
EncryptionAlgorithm = EncryptionAlgorithm.AES_256_CBC,
ValidationAlgorithm = ValidationAlgorithm.HMACSHA256
};
var interopProvider = DataProtectionProvider.Create(
new DirectoryInfo(keyRingSharePath),
builder =>
{
builder.SetApplicationName(appName);
builder.SetDefaultKeyLifetime(TimeSpan.FromDays(365 * 20));
builder.UseCryptographicAlgorithms(encryptionSettings);
if (!generateNewKey)
{
builder.DisableAutomaticKeyGeneration();
}
});
ShimmedDataProtector = new DataProtectorShim(
interopProvider.CreateProtector(
"Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware",
appName,
"v2"));
I log in using this app, confirm I have a cookie named SiteIdentity then switch to a new dotnet core app running on the same domain.
There, without adding authentication middleware I can confirm that I can unprotect and deserialize the cookie. I do this by setting up data protection in Startup to match the other app:
var appName = "SiteIdentity";
services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo(keyRingSharePath))
.SetDefaultKeyLifetime(TimeSpan.FromDays(365 * 20))
.DisableAutomaticKeyGeneration()
.UseCryptographicAlgorithms(new AuthenticatedEncryptorConfiguration()
{
EncryptionAlgorithm = EncryptionAlgorithm.AES_256_CBC,
ValidationAlgorithm = ValidationAlgorithm.HMACSHA256
})
.SetApplicationName(appName);
Then in my controller I can use a data protector to manually unprotect the cookie:
var appName = "SiteIdentity";
var protector = _dataProtectionProvider.CreateProtector(
"Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware",
appName,
"v2");
var cookieValue = Request.Cookies[appName];
var format = new TicketDataFormat(protector);
var ticket = format.Unprotect(cookieValue);
I can confirm that ticket.Principal does indeed reference a claims principal representing the account which I signed in with on the other app.
However, I've found it impossible to wire up the cookie authentication middleware to properly protect my endpoints using this cookie. This is what I've added to Startup, after the data protection code above:
var protectionProvider = services.BuildServiceProvider().GetService<IDataProtectionProvider>();
var dataProtector = protectionProvider.CreateProtector(
"Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware",
appName,
"v2");
services
.AddAuthentication(appName)
.AddCookie(appName, options =>
{
options.TicketDataFormat = new TicketDataFormat(dataProtector);
options.Cookie.Name = appName;
});
By my understanding this is telling the middleware that I have an authentication scheme named "SiteIdentity" (the advice is that authentication scheme must match the ASP.NET authentication type) which expects a cookie also called "SiteIdentity" which will contain protected data that the supplied data protector can interpret.
But when I add the attribute [Authorize(AuthenticationSchemes = "SiteIdentity")] to my controller I'm kicked away to a login page.
I can't understand what I'm doing wrong. As I've shown, I can confirm that it is indeed possible to use this data protector and ticket format to interpret the authentication cookie, so I guess I must have something wrong in this middleware wiring, but I'm not sure what.
Please ignore. It turns out that my code is actually correct. I had been working on this solution for long enough that the session represented by the cookie value I was using to test had expireed. Will leave this question here in case the code benefits anyone trying to achieve the same.
I have an Owin app that uses Ws-Federation authentication with a SSO application (not ADFS). Whenever my Owin app receives a request, it authenticates the user by first checking if it has the right cookie, and then it builds the claims and authentication ticket from that. If it doesn't have the right cookie, it redirects to the STS, which passes back a SAML token that can be used to complete the authentication.
All of this works except one part. After the token is received and validated, for some reason it redirects back to the STS, thus creating an infinite loop. I am quite sure it is because one or more of my configuration values is wrong since it's not very clear what each property is for and if it's required. I've copied my configuration code below:
public void Configuration(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType( CookieAuthenticationDefaults.AuthenticationType);
CookieAuthenticationOptions cookieOptions = new CookieAuthenticationOptions
{
AuthenticationType = CookieAuthenticationDefaults.AuthenticationType,
CookieName = "MyCookie",
CookiePath = "/CookiePath",
AuthenticationMode = AuthenticationMode.Active
};
// Basically the same as saying app.UseCookieAuthentication(app, cookieOptions)
app.Use(typeof(MyCustomCookieAuthenticationMiddleware), app, cookieOptions);
// Define properties for WsFederationAuthenticationOptions
var config = new Microsoft.IdentityModel.Protocols.WsFederationConfiguration
{
Issuer = "https://sts-domain.com/STS/",
TokenEndpoint = this.owinServerUrl // I don't know what this should be. I just made it the same as my owin start url
};
Saml2SecurityTokenHandler handler = new Saml2SecurityTokenHandler
{
Configuration = new SecurityTokenHandlerConfiguration
{
IssuerTokenResolver = new MyCustomSecurityTokenResolver
{
Thumbprint = somePublicKeyStr,
StoreLocation = System.Security.Cryptography.X509Certificates.StoreLocation.LocalMachine,
StoreName = "My"
}
}
};
var handlers = new SecurityTokenHandlerCollection(new List<SecurityTokenHandler>() { handler });
var wsFedOptions = new WsFederationAuthenticationOptions
{
AuthenticationType = WsFederationAuthenticationDefaults.AuthenticationType,
AuthenticationMode = AuthenticationMode.Passive,
SignInAsAuthenticationType = CookieAuthenticationDefaults.AuthenticationType, // I'm not sure what this is exactly, but I think I've seen this used in examples
Configuration = config,
Wtrealm = this.owinServerUrl, // I'm guessing what this should be
Wreply = someString, // I have no idea what this should be -- it hasn't seemed to have any effect so far
SecurityTokenHandlers = handlers,
TokenValidationParameters = new TokenValidationParameters
{
AuthenticationType = CookieAuthenticationDefaults.AuthenticationType, // I'm not sure whether this should be cookie or ws federation, but I don't think it's relevant to my problem
ValidIssuer = "https://sts-domain.com/STS/", // same as config.Issuer above
}
};
app.UseWsFederationAuthentication(wsFedOptions);
AuthenticateAllRequests(app, WsFederationAuthenticationDefaults.AuthenticationType);
app.Use<MyCustomMiddleware>();
}
private static void AuthenticateAllRequests(IAppBuilder app, params string[] authenticationTypes)
{
app.Use((context, continuation) =>
{
if (context.Authentication.User?.Identity?.IsAuthenticated ?? false)
{
return continuation();
}
else
{
context.Authentication.Challenge(authenticationTypes);
return Task.CompletedTask;
}
});
}
As I indicate in my code with my comments, there are some properties I am unsure of. Unfortunately, the documentation for WsFederationAuthenticationOptions does not help me out much. For example, I know that Wtrealm and Wreply are important (perhaps Wreply less so), but all the documentation says is "Gets or sets the 'wtrealm'" and "Gets or sets the 'wreply'." I found this thread that has an explanation:
wtrealm is a URI (not necessarily a URL) that identifies the RP. The STS uses this to decide whether to issue a token and what claims to give it.
wreply is the URL that the RP would like to be redirected to with the resulting token. The STS is not bound to comply with this request... sometimes the STS has a predefined address it will redirect to based on the established trust. At the very least, the STS should refuse to redirect to a different domain than the one it associates with the realm. Otherwise, the request could be a vector to send the user to a malicious site.
This makes sense, except for the fact that while I've been testing my owin app, Wreply seems to have no effect on where the STS is redirected to pass back the token; the URL I put for Wtrealm is what determines that.
All I'd like to do is for the STS to pass back the token, authenticate the user, and carry on to the route that the user specified which started all this. I'm not sure if this is relevant, but I also thought that the cookie is supposed to be set when the STS passes back the token. If this were the case, the infinite redirect wouldn't occur because when it comes back to be authenticated, the cookie authentication would find the cookie and the app would proceed as normal.
Update 1
I have changed a few of the values and have gotten different error messages, so I thought I'd share them here in case they help shed light on what could be going on. As you can tell from my post, I don't want to share real info about the app, so bear with me. Let's say the overarching web application (what contains my owin app, along with a bunch of other stuff) has the url http://localhost/app. My owin app has the server url (what I call this.owinServerUrl in the code above) http://localhost/app/owin.
When I make WsFederationConfiguration.TokenEndpoint = "http://localhost/app", I get the infinite redirect. When I make it "http://localhost/app/owin", I don't get an infinite redirect, but I do get another error (which error I get depends on other values, which I'll explain now).
I was mistaken -- Wreply does seem to have an effect. When I don't set Wreply, I get a 414 error: request URL too long. When I do set it (and as any string, whether it's a URL that I think could make sense or just gibberish), I get a 400 bad request: request too long.
Isn't it because of your AuthenticateAllRequests function? I think you think it only runs after the user is authenticated and context.Authentication.User?.Identity?.IsAuthenticated is set. However, I think it runs after the user is authenticated and before context.Authentication.User?.Identity?.IsAuthenticated is set. Since is never set, does it just invoke authentication again by calling "context.Authentication.Challenge(authenticationTypes)" ?
I have a WebAPI with OAuth login configured like this:
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = authority,
PostLogoutRedirectUri = "https://www.microsoft.com/",
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = context =>
{
context.HandleResponse();
context.Response.Redirect("/Error?message=" + context.Exception.Message);
return Task.FromResult(0);
}
}
});
and Login enforced for all Controllers using
config.Filters.Add(new System.Web.Http.AuthorizeAttribute());
I now want to add an ApiController called LogoutController (guess what it does).
I have found that I can logout from MVC using
System.Web.Security.FormsAuthentication.SignOut();
but I am not logged out from WebAPI that way. I have not found any information how to logout from WebAPI. But I have found that there may be a bug in logout procedure, the cookie is kept and has to be removed manually, but then, the code is MVC again, and it seems as if I can't get a HttpCookie into my HttpResponseMessage object:
[HttpGet]
public HttpResponseMessage Logout()
{
FormsAuthentication.SignOut();
// clear authentication cookie
HttpCookie cookie1 = new HttpCookie(FormsAuthentication.FormsCookieName, "");
cookie1.Expires = DateTime.Now.AddYears(-1);
var response = Request.CreateResponse(HttpStatusCode.OK);
response.Content = new StringContent("<html><title>Logout successful</title><body style=\"font-family:sans-serif\"><div style=\"display:table; width:100%; height:100%; margin:0; padding:0; \"><div style=\"display:table-cell; vertical-align:middle; text-align:center;\">You have been successfully logged out.<br>You can close this window/tab now.</div></div></body></html>");
response.Headers.AddCookies(cookie1); // Types don't match
return response;
}
How can I achieve that my WebAPI is logged out and does require OAuth to be done again before I am logged in?
You can't logout of the API because you're not logged in to it!
For example, say your API uses Facebook as its OpenID authentication provider.
Your user will have to log into facebook to use your API. Your API will redirect them to facebook auth server and if they are not logged in - facebook will ask them to log in.
If the user decides to stay logged into facebook, then each time they use your API, they will not be required to login to facebook again and your middleware code will obtain a valid token for them to access your API.
Your API can't remove the browser cookie between facebook and your user's browser so you can't log them out of facebook, so you can't stop them getting new tokens when they want.
I don't know what OpenID provider you use but I would think the above applies for any.
You can log out of MVC app as it would have created a cookie between you (user agent) and the MVC app when you logged in. It can delete its own cookie!
The easiest way is for the client itself to just "forget" the token - no need to tell server about it (this is what clearing the auth cookie really is doing - making the browser remove the cookie).
If you want the token itself to be no longer valid, than you would need to maintain a list of revoked tokens. For various reasons you may want your access tokens to be always valid but short lived and revoke refresh tokens instead.
I am currently struggling with setting the timeout on the cookie/auth token when authenticating my .NET Core App using Azure AD via the OpenIdConnect authentication model.
The sign-in scheme is being set in the ConfigureServices method via the following:
services.AddAuthentication(options => options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme);
I am then setting up my configuration as follows:
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
CookieName = "MyCookie",
ExpireTimeSpan = TimeSpan.FromHours(2)
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions()
{
Authority = authorityUri.AbsoluteUri,
ClientId = azureOptions.ClientId,
ClientSecret = azureOptions.ClientSecret,
ResponseType = OpenIdConnectResponseTypes.CodeIdToken,
Events = new OpenIdConnectEvents()
{
OnAuthorizationCodeReceived = async context =>
{
await aAuthenticateMiddleware.OnAuthenticate(context, logger);
}
}
});
app.UseMiddleware<aAuthenticateMiddleware>();
Note, that I am not using the built in Identity (as its not practical for our purposes) but rather using a custom middleware.
Within the middleware layer I am checking whether the user is authenticated and if not a challenge is issued:
var authenticationProperties = new AuthenticationProperties() { RedirectUri = context.Request.Path.Value ?? "/" };
authenticationProperties.AllowRefresh = false;
authenticationProperties.IssuedUtc = DateTime.Now;
authenticationProperties.ExpiresUtc = DateTime.Now.AddHours(2);
await context.Authentication.ChallengeAsync(
authenticationManager.IdentityProvider.AuthenticationScheme,
authenticationProperties,
ChallengeBehavior.Automatic
);
This is all works fine and authenticates the user correctly etc however this is issuing the auth token (and cookie) with a 15 minute expiry and ignoring my 2 hour expiry that I have tried setting.
I have been referring to the latest source examples from GitHub from the aspnet/security repository for examples.... however none of these mention anything about overriding the default expiry issued.
https://github.com/aspnet/Security/tree/dev/samples/OpenIdConnect.AzureAdSample
Most examples I have found are still referencing the old AspNet libraries rather than the AspNetCore libraries.
Some articles suggest that using the SignInAsync with persistent set to True allows the ExpireTimeSpan to be honored, however this throws a "Not Supported Exception" when calling it. Perhaps SignInAsync is not supported via Azure AD?
Does anyone have any insight on how to achieve this?
in UseOpenIdConnectAuthentication set UseTokenLifetime = false
How can I retrieve the OpenID connect token from the cookie(s) produced by Microsoft's OWIN-based middleware?
I am using Microsoft.Owin.Security.Cookies and Microsoft.Owin.Security.OpenIdConnect to protect a website using an 'implicit flow'. At times I think I might be able understand things better or be able to troubleshoot i I could inspect the "raw" token rather than the object model that gets produced from it.
I understand the information is stored via Cookie, but have not found how I can I retrieve the token from the cookie(s). This is a development environment so I should have access to any certificates/secrets that are needed.
I understand that the token should have 3 segments separated by periods: {header}.{claims}.{signature}. If I can find the token I have learned that I can use jwt.io to view the contents. However, none of my cookies have contents matching that format.
This is the middleware configuration I am using:
app.SetDefaultSignInAsAuthenticationType( CookieAuthenticationDefaults.AuthenticationType );
app.UseCookieAuthentication( new CookieAuthenticationOptions() );
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = stsAuthority,
RedirectUri = baseHostingPath,
ResponseType = "id_token",
Scope = string.Join( " ", "openid", "profile", "email" )
} );
If you want to do this just at debug time, I would suggest giving a try to https://github.com/vibronet/OInspector/tree/dev - it helps you to inspect the token in Fiddler.
If you want to do this in code, you can ensure that the raw token is saved in the ClaimsPrincipal by
Adding
TokenValidationParameters = new TokenValidationParameters
{
SaveSigninToken = true
}
to the options initialization
Retrieving the token via something to the effect of
var ci = (System.Security.Claims.ClaimsIdentity)
ClaimsPrincipal.Current.Identity;
string token = ((System.IdentityModel.Tokens.BootstrapContext)
ci.BootstrapContext).Token;