HttpContext.Current is null after Azure AD authentication success - c#

Question: What could be causing HttpContext.Current to be null only sometimes?
Question: Is HttpContext.Current initialized after PrincipalService.OnAzureAuthenticationSuccess is called? If so, why only sometiems?
Description
What happens is quite often a user will click sign-in and HttpContext.Current will be null causing the cookie to be never set. Which redirects them back to the home-page and since the cookie wasn't set they click sign-in again and again and again. Sometimes it'll decide to let the cookie be set after 2 or 3 clicks other times it won't do it without clearing cookies or logging out of another Azure AD account (e.g. Our sharepoint server uses Azure AD).
These things seem so odd to me and I haven't been able to pin down what the cause is despite hours of research into it.
Azure Config
public static void ConfigureAzure(IAppBuilder app)
{
// COOKIES: Tells it to use cookies for authentication.
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
// CUSTOMIZE: This is where you would adjust cookie experiation and things of that nature.
SlidingExpiration = true,
ExpireTimeSpan = TimeSpan.FromHours(CookieDurationInHours)
});
//https://azure.microsoft.com/en-us/resources/samples/active-directory-dotnet-webapp-webapi-openidconnect/
// OPEN-ID: Handle OpenID stuff.
var notifications = new OpenIdConnectAuthenticationNotifications()
{
AuthenticationFailed = PrincipalService.OnAzureAuthenticationFailure,
// REFERENCE: https://russellyoung.net/2015/09/05/mvc-role-based-authorization-with-azure-active-directory-aad/
AuthorizationCodeReceived = PrincipalService.OnAzureAuthenticationSuccess
};
var options = new OpenIdConnectAuthenticationOptions()
{
ClientId = ClientID,
Authority = Authority,
PostLogoutRedirectUri = PostLogoutRedirectUri,
Notifications = notifications
};
app.UseOpenIdConnectAuthentication(options);
}
On Azure Success
/// <summary>
/// Stores the proper identity cookie (doesn't have customer permissions yet).
/// </summary>
public static Task OnAzureAuthenticationSuccess(AuthorizationCodeReceivedNotification context)
{
var success = false;
var username = context.AuthenticationTicket.Identity.Name;
try
{
success = StoreCookie(username);
}
catch (DbEntityValidationException ex)
{
var errors = ex.EntityValidationErrors.FirstOrDefault()?.ValidationErrors.FirstOrDefault()?.ErrorMessage;
Logger.Log(Level.Error, "An error occurred while storing authentication cookie.", ex);
return Task.FromResult(0);
}
catch (Exception ex)
{
Logger.Log(Level.Error, "An error occurred while storing authentication cookie.", ex);
return Task.FromResult(0);
}
if (success)
{
Logger.Log(Level.Cookie, "Login Complete. Cookie stored successfully. Username: '" + username + "'.");
}
return Task.FromResult(0);
}
Store Cookie
/// <summary>
/// Creates and stores a forms authentication cookie for the user.
/// </summary>
private static bool StoreCookie(string username, bool rememberMe = false)
{
var azureUsers = new AzureUserRepository(new AuthenticationEntities());
var user = azureUsers.Get(u => u.Username == username);
if (user == null)
{
Logger.Log(Level.Cookie, "User '" + username + "' not found.");
throw new NullReferenceException();
}
// Clear any old existing cookies.
if (HttpContext.Current == null)
{
// HERE: This is where it is null (again, only sometimes).
Logger.Log(Level.Debug, "HttpContext is null.");
return false;
}
if (HttpContext.Current.Request == null)
{
Logger.Log(Level.Debug, "HttpContext.Current.Request is null.");
return false;
}
if (HttpContext.Current == null && HttpContext.Current.Response != null)
{
Logger.Log(Level.Debug, "HttpContext.Current.Response is null.");
return false;
}
HttpContext.Current.Request.RemoveFormsAuthCookie();
HttpContext.Current.Response.RemoveFormsAuthCookie();
// Create the principal from the user object.
var principal = new PrincipalModel(user);
// Create and store the cookie in the response.
HttpContext.Current.Response.AddFormsAuthCookie(
username: user.Username,
userData: principal.SerializeUserData(),
isPersistent: true
);
return true;
}
AccountController
[AllowAnonymous]
public void SignIn()
{
if (Request.IsAuthenticated) { return; }
HttpContext.GetOwinContext().Authentication.Challenge(
new AuthenticationProperties() { RedirectUri = "/" }, OpenIdConnectAuthenticationDefaults.AuthenticationType
);
Logger.Log(Level.Info, "Sign-In clicked.");
}
public void SignOut()
{
if (!Request.IsAuthenticated) { return; }
// SIGN OUT:
HttpContext.GetOwinContext().Authentication.SignOut(
OpenIdConnectAuthenticationDefaults.AuthenticationType, CookieAuthenticationDefaults.AuthenticationType
);
Logger.Log(Level.Info, "Sign-out clicked.");
// COOKIE: Remove the cookie.
var cookie = Request.Cookies[FormsAuthentication.FormsCookieName];
cookie.Expires = DateTime.Now.AddDays(-1); // DateTime.UtcNow.AddDays(-1);
Response.Cookies.Add(cookie);
}

Alright, so it turns out I was being a big dummy and doing things the hard way. I don't 100% fully understand why it was null but I did find a much simpler way around the issue.
Started by removing the code related to me creating my own cookie (i.e. AuthorizationCodeReceived = PrincipalService.OnAzureAuthenticationSuccess).
What happened was I realized that Azure AD was creating its own principal and cookie via the app.UseCookieAuthentication(new CookieAuthenticationOptions()); This realization happened thanks to the git hub project that Fei Xue linked.
After that I switched my custom principal to create based on the 'built-in' cookie instead of the cookie I was creating (which was only getting created half of the time due to HttpContext.Current being null).
Now that the custom principal creation wasn't dependent on HttpContext.Current I didn't have a sign-in loop happening at all because the principal and cookie both exist.
Thanks a ton to Fei Xue!

Related

IdentityServer shows blank PostLogoutRedirectUri for Android Native App

I have created an OAuth Server using IdentityServer4 and .Net Core Signin Manager. The Login works great and returns to my app. The Logout doesn't seem to know who is logging out. The Logout Razor Page code is as follows:
public async Task<IActionResult> OnGet(string logoutId)
{
var logout = await _interaction.GetLogoutContextAsync(logoutId);
PostLogoutRedirectUri = logout?.PostLogoutRedirectUri;
AutomaticRedirectAfterSignOut = (PostLogoutRedirectUri != null);
ClientName = string.IsNullOrEmpty(logout?.ClientName) ? logout?.ClientId : logout?.ClientName;
SignOutIframeUrl = logout?.SignOutIFrameUrl;
LogoutId = logoutId;
if (User?.Identity.IsAuthenticated == true)
{
var idp = User.FindFirst(JwtClaimTypes.IdentityProvider)?.Value;
if (idp != null && idp != IdentityServer4.IdentityServerConstants.LocalIdentityProvider)
{
var providerSupportsSignout = await HttpContext.GetSchemeSupportsSignOutAsync(idp);
if (providerSupportsSignout)
{
if (LogoutId == null)
{
// if there's no current logout context, we need to create one
// this captures necessary info from the current logged in user
// before we signout and redirect away to the external IdP for signout
LogoutId = await _interaction.CreateLogoutContextAsync();
}
ExternalAuthenticationScheme = idp;
}
}
// delete local authentication cookie
await _signInManager.SignOutAsync();
// raise the logout event
await _events.RaiseAsync(new UserLogoutSuccessEvent(User.GetSubjectId(), User.GetDisplayName()));
}
// check if we need to trigger sign-out at an upstream identity provider
if (TriggerExternalSignout)
{
// build a return URL so the upstream provider will redirect back
// to us after the user has logged out. this allows us to then
// complete our single sign-out processing.
string url = Url.Action("Logout", new { logoutId = LogoutId });
// this triggers a redirect to the external provider for sign-out
return SignOut(new AuthenticationProperties { RedirectUri = url }, ExternalAuthenticationScheme);
}
if (AutomaticRedirectAfterSignOut)
return Redirect(PostLogoutRedirectUri);
else
return Page();
}
When it gets called, there is a logoutId. It gets the context, but PostLogoutRedirectUri is blank. ClientId and ClientName are also blank, but the context has a field called ClientIds and the first entry is the correct ClientId for my app. The log shows as follows:
IdentityServer4.Validation.EndSessionRequestValidator: Information: End session request validation success
{
"SubjectId": "6841dc6c-0bd7-4f72-8f1c-f7czzzzzzzzz",
"Raw": {
"post_logout_redirect_uri": "mps.mobile.app://callback"
}
}
IdentityServer4.Hosting.IdentityServerMiddleware: Information: Invoking IdentityServer endpoint: IdentityServer4.Endpoints.EndSessionCallbackEndpoint for /connect/endsession/callback
IdentityServer4.Endpoints.EndSessionCallbackEndpoint: Information: Successful signout callback.
I am using IdentityModel for the Client App. I have the logout coded as follows:
_options = new OidcClientOptions
{
Authority = MPSOidc.Authority,
ClientId = MPSOidc.ClientID,
Scope = "openid profile myapi offline_access email",
RedirectUri = MPSOidc.RedirectUri,
PostLogoutRedirectUri = MPSOidc.RedirectUri,
ResponseMode = OidcClientOptions.AuthorizeResponseMode.Redirect,
Browser = new ChromeCustomTabsBrowser(this)
};
var oidcClient = new OidcClient(_options);
var r = new LogoutRequest();
await oidcClient.LogoutAsync(r);
It seems like the PostLogoutRedirectUri should show up here. Does anyone know a way to make this happen? If not, can the ClientId be used to get the Client information to find the PostLogoutRedirectUri there?
Thanks,
Jim
Here is what it was. When I logged out on the OidcClient, I didn't pass the ID Token. On my client Android app, I had to add the ID Token to the logout request:
var r = new LogoutRequest()
{
IdTokenHint = MPSOidc.Tokens.IdentityToken
};
That's all it took.
Cheers.

IdentityServer4 Windows Sign-On under IIS 10 fails to authenticate successfully

Using IIS Express on my local machine, I'm able to run the IdentityServer4 QuickStart UI project and successfully sign in. However, once it is deployed to production, I'm unable to get it to work.
On the Application Pool for the site, I have a domain account setup (with just about every permission possible granted). I have tried every variation of having "anonymous authentication" toggled. I've gone as far as recreating the entire application from scratch in multiple different ways (no-SSL, only-SSL, fully open CORS, all security policies disabled), and even the most basic version of the application seems to suffer from the exact same issue.
After slapping some logging on the application, I can see that I'm grabbing the Subject ID and name from AD just fine.
Here's the ProcessWindowsLoginAsync method, with only minimal logging changes.
private async Task<IActionResult> ProcessWindowsLoginAsync(string returnUrl)
{
var result = await HttpContext.AuthenticateAsync(AccountOptions.WindowsAuthenticationSchemeName);
if (result?.Principal is WindowsPrincipal wp)
{
var props = new AuthenticationProperties
{
RedirectUri = Url.Action("Callback"),
Items =
{
{ "returnUrl", returnUrl },
{ "scheme", AccountOptions.WindowsAuthenticationSchemeName },
}
};
var id = new ClaimsIdentity(AccountOptions.WindowsAuthenticationSchemeName);
var sub = wp.FindFirst(ClaimTypes.PrimarySid).Value;
id.AddClaim(new Claim(JwtClaimTypes.Subject, sub));
id.AddClaim(new Claim(JwtClaimTypes.Name, wp.Identity.Name));
_logger.LogInformation("Assigning claims. Subject {#Subject}. Name {#Name}", sub, wp.Identity.Name);
if (AccountOptions.IncludeWindowsGroups)
{
var wi = wp.Identity as WindowsIdentity;
var groups = wi!.Groups!.Translate(typeof(NTAccount));
var roles = groups!.Select(x => new Claim(JwtClaimTypes.Role, x.Value));
id.AddClaims(roles);
}
await HttpContext.SignInAsync(
IdentityServerConstants.ExternalCookieAuthenticationScheme,
new ClaimsPrincipal(id),
props);
return Redirect(props.RedirectUri);
}
return Challenge(AccountOptions.WindowsAuthenticationSchemeName);
}
The above code spits out something akin to (with identifying information stripped):
Assigning claims. Subject S-0-0-00-0000000000-0000000000-0000000000-00000. Name DOMAIN\NAME
Once the above has executed, the external callback method is called and it immediately throws an exception:
[HttpGet]
public async Task<IActionResult> Callback()
{
var result = await HttpContext.AuthenticateAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme);
if (result?.Succeeded != true)
{
_logger.LogInformation("We were not successfully able to sign in. Failure: {#Failure}. None: {#None}", result?.Failure, result?.None);
if (result?.Failure != null)
throw result.Failure;
throw new Exception("External authentication error");
}
if (_logger.IsEnabled(LogLevel.Debug))
{
var externalClaims = result.Principal.Claims.Select(c => $"{c.Type}: {c.Value}");
_logger.LogDebug("External claims: {#claims}", externalClaims);
}
var (user, provider, providerUserId, claims) = FindUserFromExternalProvider(result);
if (user == null)
user = AutoProvisionUser(provider, providerUserId, claims);
var additionalLocalClaims = new List<Claim>();
var localSignInProps = new AuthenticationProperties();
ProcessLoginCallbackForOidc(result, additionalLocalClaims, localSignInProps);
var issuer = new IdentityServerUser(user.SubjectId)
{
DisplayName = user.Username,
IdentityProvider = provider,
AdditionalClaims = additionalLocalClaims
};
await HttpContext.SignInAsync(issuer, localSignInProps);
await HttpContext.SignOutAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme);
var returnUrl = result.Properties.Items["returnUrl"] ?? "~/";
var context = await _interaction.GetAuthorizationContextAsync(returnUrl);
await _events.RaiseAsync(new UserLoginSuccessEvent(provider, providerUserId, user.SubjectId, user.Username, true, context?.ClientId));
if (context != null)
if (await _clientStore.IsPkceClientAsync(context.ClientId))
return this.LoadingPage("Redirect", returnUrl);
return Redirect(returnUrl);
}
From the logs, I can tell that it's immediately failing after attempting to authenticate. There's no other errors, but a few interesting logs of note (in order):
Performing protect operation to key {xxxxxxxx-xxxx-xxxx-xxxx-b7e4d6dd250a} with purposes ('C:\websites\identity.ourdomain.com', 'Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware', 'idsrv.external', 'v2').
AuthenticationScheme: idsrv.external signed in.
Executing RedirectResult, redirecting to /External/Callback
Executing action method IdentityServer4.Quickstart.UI.ExternalController.Callback (Idsvr.Api) - Validation state: Valid
AuthenticationScheme: idsrv.external was not authenticated.
(Exception)
One of the possible root cause is that the callback cookie doesn't set properly.
Try to capture the network traffic, and check if idsrv.external cookie has been set correctly during Challenge.
In my case, setting cookie failed because SameSite=None is there without Secure=true.

How to validate if user exist inside IdentityServer4 after being authenticated from External Provider?

I'm trying to find a proper way where I can inject a service to validate if user exists or registered in my application after being successfully authenticated from an external identity provider like Azure Active Directory. What I want to do is to redirect user to a custom error page or display an Unauthorized message if his account is not yet registered in my application.
I tried utilizing the IProfileService interface but it seems not the right way to go.
Here is my Startup.cs setup:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services
.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddTestUsers(Config.GetUsers())
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddInMemoryApiResources(Config.GetApiResources())
.AddInMemoryClients(Config.GetClients()) // Client was configured with RequireConsent = false, EnableLocalLogin = false,
.AddProfileService<ProfileService>()
.Services.AddTransient<IUserRepository,UserRepository>();
services.AddAuthentication()
.AddOpenIdConnect("AAD", "Azure Active Directory", options =>
{
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
options.SignOutScheme = IdentityServerConstants.SignoutScheme;
options.Authority = "https://login.microsoftonline.com/MyTenant";
options.ClientId = "MyClientId";
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false
};
options.GetClaimsFromUserInfoEndpoint = true;
});
}
public class ProfileService : IProfileService
{
private readonly IUserRepository _userRepository;
public ProfileService(IUserRepository userRepository)
{
_userRepository = userRepository
}
public Task GetProfileDataAsync(ProfileDataRequestContext context)
{
var user = _userRepository.FindByUser(context.Subject.Identity.Name);
// This will display HTTP 500 instead of 401
if(user == null) throw new UnauthorizedAccessException("You're not registered");
// I add custom claims here
return Task.FromResult(0);
}
public Task IsActiveAsync(IsActiveContext context) => Task.FromResult(0);
}
Is there any available service or interface I can use where I can inject my user validation as well as allowing me to inject my user repository in that service? Is it possible to inject this kind of process inside IdentityServer4? Can someone point me in the right direction to accomplish my goal using IdentityServer4?
Note: Lets assume I have SPA web app and I have my own separate registration mechanism. I don't want to redirect back to my SPA if user doesn't exist and handle it inside IdentityServer4 instead. Btw, some of the code above are not included for brevity.
The IdentityServer4 QuickStart UI is configured to auto-provision local user accounts when signing-in through an external provider. That's all handled in ExternalController.Callback:
// lookup our user and external provider info
var (user, provider, providerUserId, claims) = FindUserFromExternalProvider(result);
if (user == null)
{
// this might be where you might initiate a custom workflow for user registration
// in this sample we don't show how that would be done, as our sample implementation
// simply auto-provisions new external user
user = AutoProvisionUser(provider, providerUserId, claims);
}
In your situation, you can perform whatever logic you need to perform instead of calling AutoProvisionUser. As this is a regular MVC action that's being executed, you have the ability to inject your own classes into ExternalController's constructor or into Callback itself (using [FromServices]). Here's a rough idea of the changes you might want to make:
public async Task<IActionResult> Callback([FromServices] IUserRepository userRepository)
{
...
// lookup our user and external provider info
var (user, provider, providerUserId, claims) = FindUserFromExternalProvider(result);
if (user == null)
{
// We don't have a local user.
return RedirectToAction("SomeAction", "SomeController");
}
...
}
You can write your custom logic in ExternalLoginCallback function in in AccountController if you are using ASP.NET Identity . After getting JWT token issued from Azure AD , you can decode the token ,get the user claims such as email/name :
if (remoteError != null)
{
ErrorMessage = $"Error from external provider: {remoteError}";
return RedirectToAction(nameof(Login));
}
// read external identity from the temporary cookie
var aadResult1 = await HttpContext.AuthenticateAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme);
if (aadResult1?.Succeeded != true)
{
throw new Exception("External authentication error");
}
// retrieve claims of the external user
var externalUser = aadResult1.Principal;
if (externalUser == null)
{
throw new Exception("External authentication error");
}
// retrieve claims of the external user
var claims = externalUser.Claims.ToList();
// try to determine the unique id of the external user - the most common claim type for that are the sub claim and the NameIdentifier
// depending on the external provider, some other claim type might be used
var userIdClaim = claims.FirstOrDefault(x => x.Type == JwtClaimTypes.Subject);
if (userIdClaim == null)
{
userIdClaim = claims.FirstOrDefault(x => x.Type == "http://schemas.microsoft.com/identity/claims/objectidentifier");
}
if (userIdClaim == null)
{
throw new Exception("Unknown userid");
}
Then you can write your service implement/logic in database to confirm whether user is already in database . If yes , login in user;if no , redirect user to confirmation/register view . Something like:
// Sign in the user with this external login provider if the user already has a login.
var result = await _signInManager.ExternalLoginSignInAsync("YourProvider", userIdClaim.Value, isPersistent: false, bypassTwoFactor: true);
if (result.Succeeded)
{
_logger.LogInformation("User logged in with {Name} provider.", "YourProvider");
// delete temporary cookie used during external authentication
await HttpContext.SignOutAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme);
return RedirectToLocal(returnUrl);
}
if (result.IsLockedOut)
{
return RedirectToAction(nameof(Lockout));
}
else
{
// If the user does not have an account, then ask the user to create an account.
ViewData["ReturnUrl"] = returnUrl;
ViewData["LoginProvider"] = "YourProvider";
var email = claims.FirstOrDefault(x => x.Type == ClaimTypes.Upn).Value;
return View("ExternalLogin", new ExternalLoginViewModel { Email = email });
}
It depends on you for how to link AD user to local database user .use Azure AD's object ID or UPN .

Authenticate user in ASP.NET MVC with identities

I am extremely confused about how the actual authentication works so that [Authorize] does not redirect me to the login page.
Here's my Configuration:
public class IdentityConfig
{
public void Configuration(IAppBuilder app)
{
app.CreatePerOwinContext(() => new MyANTon.DataContext.AntContext());
app.CreatePerOwinContext<UserManager>(UserManager.Create);
app.CreatePerOwinContext<RoleManager<AppRole>>((options, context) =>
new RoleManager<AppRole>(
new RoleStore<AppRole>(context.Get<MyANTon.DataContext.AntContext>())));
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Home/Login"),
});
}
}
In the controller, I want to call an Authenticate(string Email, String Password) method, authenticate the user and return a bool. However, I have no idea how the actual authentication works.
In FormsAuthentication I would create a ticket, what do I do for Identity?
Here's what I have:
public static async System.Threading.Tasks.Task<bool> AuthUserAsync(string Email, string Password)
{
using (var db = new AntContext())
{
string hashedPW = GetHash(Password);
bool userValid = db.Users.Any(user => user.Email == Email && user.Password == hashedPW);
if (userValid)
{
var actUser = db.Users.FirstOrDefault(u => u.Email == Email && u.Password == hashedPW);
if (actUser != null && !actUser.IsLocked)
{
/** What do I do here? **/
}
else if (actUser.IsLocked)
{
LoggingServices.AuthLog(actUser.Email, "Hat versucht auf ein gesperrtes Konto zuzugreifen.");
}
}
return false;
}
}
You are heading in the right direction, what you are doing is sort of using OAuth to faciliate the mapping of your tokens and letting OWin handle the browser information. So, by using the [Authorize] attribute, like you are doing, how are you handling the signing of the identity? Like mentioned above with Forms authentication, you still have to create the Identity/Claim token. In my projects, I do something like this,
protected void IdentitySignin(IUserModel userModel, string providerKey = null, bool isPersistent = true)
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, userModel.Id.ToString()),
new Claim(ClaimTypes.Name, userModel.UserName),
new Claim("UserContext", userModel.ToString())
};
var identity = new ClaimsIdentity(claims, DefaultAuthenticationTypes.ApplicationCookie);
AuthenticationManager.SignIn(new AuthenticationProperties
{
IsPersistent = isPersistent,
ExpiresUtc = DateTime.UtcNow.AddDays(7)
}, identity);
}
This forces OWIN/OAuth to login the user, and in my web.config I have the following:
<system.webserver>
<authentication mode="None" />
</system.webserver>
And to sign my user out, and force the browser to get a new token:
protected void IdentitySignout()
{
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie,
DefaultAuthenticationTypes.ExternalCookie);
}
My AuthenticationManager is defined as such:
private IAuthenticationManager AuthenticationManager
{
get { return HttpContext.GetOwinContext().Authentication; }
}
Which is part of Microsoft.OWin.IOwinContext, you will have to add the reference if it does not exist.
You can handle an unauthorized user via the web.config file, or a base-controller, I opted for the base-controller option like so:
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (UserContext == null || UserContext.Id.Equals(Guid.Empty))
{
if (filterContext.Controller.GetType() == typeof(AccountController) || filterContext.Controller.GetType() == typeof(HomeController)) return;
filterContext.Result = new RedirectResult("/Home/Index");
return;
}
}
But you can also do it via the AuthorizeAttribute if you wanted. This link will go into great detail on handling Oauth and Asp.Net MVC, may seem daunting at first, but it gives a great layout on using other providers if you decided to incorporate them into a release version.
https://www.codeproject.com/Articles/577384/Introduction-to-OAuth-in-ASP-NET-MVC
When you want Login in to your web-site you send your token to client and you can work with that to request and response in many, in other word, you have to login with your server side.
But when you want to logout from web-site, your client have to know that you want to logout this is not work just for server, the client should do this issue for log out.
I want to suggest you Token-JWT
if you want know about JWT click here
If you want I'll create an Example for you.

Force expiration of authentication token

I've spent a week now securing my Web API, creating custom filters and uses of authentication tokens. My problem now was when I'm requesting in my Web API using POSTMAN and the user was already sign out I can still get values from my API.
How can i manage to force expire my access token? Or is there other way to manage this kind of situation?
NOTE: When I'm requesting using POSTMAN, I copied my access token from the local storage.
Update:
This is what i followed in creating access token. http://www.asp.net/web-api/overview/security/individual-accounts-in-web-api
I tried the same situation as mine to the downloaded solution, still my access token is authenticated
You need to remove the cookies and the session if sign out is not doing that.
FormsAuthentication.SignOut();
Session.Abandon();
// clear authentication cookie
HttpCookie cookie1 = new HttpCookie(FormsAuthentication.FormsCookieName, "");
cookie1.Expires = DateTime.Now.AddYears(-1);
Response.Cookies.Add(cookie1);
// clear session cookie (not necessary for your current problem but i would recommend you do it anyway)
HttpCookie cookie2 = new HttpCookie("ASP.NET_SessionId", "");
cookie2.Expires = DateTime.Now.AddYears(-1);
Response.Cookies.Add(cookie2);
FormsAuthentication.RedirectToLoginPage();
Refrence Taken from here
As per http://www.asp.net/web-api/overview/security/individual-accounts-in-web-api
In this article all the details regarding authorization token is stored on session along with cookies. So you have two ways to sort out this.
Clear all the session and cookies on the logout.
You can also make a custom autorization filter and generate custom access token and store them to local file or database with the timeout limit. On logout you can clear tokens as per the user.
here is an example, how to set custom filters in web api 2.
public class CustomAuthenticateAttribute : Attribute, IAuthenticationFilter
{
public async Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
{
HttpRequestMessage request = context.Request;
AuthenticationHeaderValue authorization = request.Headers.Authorization;
if (authorization == null)
return;
if (authorization.Scheme != "Bearer")
return;
if (String.IsNullOrEmpty(authorization.Parameter))
{
context.ErrorResult = new AuthenticationFailureResult("Missing token", request);
return;
}
TokenL1 tokenL1;
var validateToken = TokenHelper.DecryptToken(authorization.Parameter, out tokenL1);
if (!validateToken)
{
context.ErrorResult = new AuthenticationFailureResult("Token invalid", request);
return;
}
if (!(tokenL1.tokenexpiry > DateTime.Now))
{
context.ErrorResult = new AuthenticationFailureResult("Token expire", request);
return;
}
IPrincipal principal = new GenericPrincipal(new GenericIdentity(tokenL1.email), new string[] { "user" });
if (principal == null)
{
context.ErrorResult = new AuthenticationFailureResult("Invalid token", request);
return;
}
else
{
context.Principal = principal;
}
}
public Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken)
{
var challenge = new AuthenticationHeaderValue("Bearer");
context.Result = new AddChallengeOnUnauthorizedResult(challenge, context.Result);
return Task.FromResult(0);
}
public bool AllowMultiple
{
get { return false; }
}
}
use this custom filer on the actionresult of a controller like this
[CustomAuthenticate]
public ActionResult Index()
{
return View();
}

Categories