Retrieving email from Facebook Login in ASP.NET MVC throwing null exception - asp.net

I am developing an ASP.NET MVC application. In my application, I need to provide Faccebook Login service to user. Besides, I need retrieve the user email from Facebook and store in the database.
I am using Visual Studio 2013 and ASP.NET MVC5. My Facebook login method is working fine. But when I retrieve email from Facebook, it is giving me null exception. My externalLoginCallback method is below:
[AllowAnonymous]
public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{
var result = await AuthenticationManager.AuthenticateAsync(DefaultAuthenticationTypes.ExternalCookie);
if (result == null || result.Identity == null)
{
return RedirectToAction("Login");
}
var idClaim = result.Identity.FindFirst(ClaimTypes.NameIdentifier);
if (idClaim == null)
{
return RedirectToAction("Login");
}
var login = new UserLoginInfo(idClaim.Issuer, idClaim.Value);
var name = result.Identity.Name == null ? "" : result.Identity.Name.Replace(" ", "");
//retrieving email from facebook here
var externalIdentity = HttpContext.GetOwinContext().Authentication.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie);
var emailClaim = externalIdentity.Result.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Email);
var email = emailClaim.Value;//this line is throwing null exception
.
.
.
}
I commented where error throw in my code.
This is how I configured Facebook in Startup.Auth.cs
var facebookAuthenticationOptions = new FacebookAuthenticationOptions()
{
AppId = "x",
AppSecret = "y"
};
facebookAuthenticationOptions.Scope.Add("email");
app.UseFacebookAuthentication(facebookAuthenticationOptions);
I also tried like this:
app.UseFacebookAuthentication(new FacebookAuthenticationOptions
{
AppId = "x",
AppSecret = "y",
Scope = { "email" },
Provider = new FacebookAuthenticationProvider
{
OnAuthenticated = context =>
{
context.Identity.AddClaim(new System.Security.Claims.Claim("FacebookAccessToken", context.AccessToken));
return Task.FromResult(true);
}
}
});
Both are not working. What is wrong with my code and how can I fix it?

You can try this:
var facebookAuthenticationOptions = new FacebookAuthenticationOptions()
{
AppId = "xxxxxxx",
AppSecret = "xxxxxx",
BackchannelHttpHandler = new FacebookBackChannelHandler(),
UserInformationEndpoint = "https://graph.facebook.com/v2.4/me?fields=id,name,email,first_name,last_name,location"
};
and then define this class:
public class FacebookBackChannelHandler : HttpClientHandler
{
protected override async System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
if (!request.RequestUri.AbsolutePath.Contains("/oauth"))
{
request.RequestUri = new Uri(request.RequestUri.AbsoluteUri.Replace("?access_token", "&access_token"));
}
var response = await base.SendAsync(request, cancellationToken);
return response;
}
}
finally, you can get email (and first_name,last_name,location) by doing the following in "ExternalLoginCallback" method in account controller:
[AllowAnonymous]
public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
if (loginInfo == null)
{
return RedirectToAction("LoginRegister");
}
// GET MAIL AND NAME
var email = loginInfo.ExternalIdentity.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Email).Value;
var name= loginInfo.ExternalIdentity.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Name).Value;

Related

Obtain user email from Mastodon API via OAuth

Is it possible to obtain user email through Mastodon Api? I'm working on adding OAuth authentication via Mastodon Api but only seem to get "id" and "display_name" using "/api/v1/accounts/verify_credentials" endpoint. I do not see a property returned for email so currently just using "acct" parameter. I'm using both "read:accounts" and "admin:read:accounts" scopes. This is for an NetCore application.
builder.Services.AddAuthentication()
.AddMicrosoftAccount("Microsoft", "Microsoft", microsoftOptions =>
{
microsoftOptions.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
microsoftOptions.ClientId = _appSettings.Authentication.Microsoft.ClientId;
microsoftOptions.ClientSecret = _appSettings.Authentication.Microsoft.ClientSecret;
})
.AddGoogle("Google", "Google", googleOptions =>
{
googleOptions.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
googleOptions.ClientId = _appSettings.Authentication.Google.ClientId;
googleOptions.ClientSecret = _appSettings.Authentication.Google.ClientSecret;
})
.AddGitHub("GitHub", "GitHub", githubOptions =>
{
githubOptions.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
githubOptions.ClientId = _appSettings.Authentication.GitHub.ClientId;
githubOptions.ClientSecret = _appSettings.Authentication.GitHub.ClientSecret;
})
.AddOAuth("Fosstodon", "Fosstodon", fosstodonOptions =>
{
fosstodonOptions.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
fosstodonOptions.ClientId = _appSettings.Authentication.Fosstodon.ClientId;
fosstodonOptions.ClientSecret = _appSettings.Authentication.Fosstodon.ClientSecret;
fosstodonOptions.CallbackPath = new PathString("/signin-fosstodon");
fosstodonOptions.AuthorizationEndpoint = _appSettings.Authentication.Fosstodon.AuthorizationEndpoint;
fosstodonOptions.TokenEndpoint = _appSettings.Authentication.Fosstodon.TokenEndpoint;
fosstodonOptions.UserInformationEndpoint = _appSettings.Authentication.Fosstodon.UserInformationEndpoint;
fosstodonOptions.SaveTokens = true;
fosstodonOptions.Scope.Add("read:accounts");
fosstodonOptions.Scope.Add("admin:read:accounts");
fosstodonOptions.ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "id");
fosstodonOptions.ClaimActions.MapJsonKey(ClaimTypes.Name, "name");
fosstodonOptions.ClaimActions.MapJsonKey(ClaimTypes.Email, "email");
fosstodonOptions.Events = new OAuthEvents
{
OnCreatingTicket = async context =>
{
var request = new HttpRequestMessage(HttpMethod.Get, context.Options.UserInformationEndpoint);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", context.AccessToken);
var response = await context.Backchannel.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, context.HttpContext.RequestAborted);
response.EnsureSuccessStatusCode();
var user = JObject.Parse(await response.Content.ReadAsStringAsync());
var identifier = user.Value<string>("id")?.Clean();
if (!string.IsNullOrEmpty(identifier))
{
context.Identity?.AddClaim(new Claim(
ClaimTypes.NameIdentifier, identifier,
ClaimValueTypes.String, context.Options.ClaimsIssuer));
}
var userName = user.Value<string>("display_name")?.Clean();
if (!string.IsNullOrEmpty(userName))
{
context.Identity?.AddClaim(new Claim(
ClaimTypes.Name, userName,
ClaimValueTypes.String, context.Options.ClaimsIssuer));
}
var userEmail = user.Value<string>("acct")?.Clean();
if (!string.IsNullOrEmpty(userEmail))
{
context.Identity?.AddClaim(new Claim(
ClaimTypes.Email, userEmail,
ClaimValueTypes.String, context.Options.ClaimsIssuer));
}
}
};
});
Really wanting to know if their is an endpoint that will return current user email address. I have looked through Mastodon Api documentation but not seeing and enpoint for this.
Found that it is not posible to obtain user email through Mastodon Api. Instead decided to handel this situation in the registration process of application authentication flow. Basicly check if a user exists with given email. If so then remove the auto provision user and add external provider information to existing user. Later will submit verify email, to email address provided, to avoid someone from stealing user account through registration.
/// <summary>
/// Handle postback from new registration
/// </summary>
/// <returns>IActionResult</returns>
[AllowAnonymous]
[HttpPost("Registration/New")]
[HttpPost("Registration/New/{id?}")]
[ValidateAntiForgeryToken]
public virtual async Task<IActionResult> New([Bind(RegistrationViewModel.BindProperties)] RegistrationViewModel model, [FromForm(Name = "Button")] string button)
{
// Check if cancled
if (button.Clean() != "submit")
return RedirectToAction("Index", "Home");
// Check email is valid
if (!model.Email.Clean().IsValidEmail())
ModelState.AddModelError(nameof(model.Email), _sharedLocalizer["ErrorMessage.Invalid"]);
if (ModelState.IsValid)
{
// setup results
IdentityResult identityResult = new IdentityResult();
// Check for existing user
ApplicationUser user = await _userManager.FindByEmailAsync(model.Email.Clean());
if (user != null)
{
if (user.Id != model.Id.Clean())
{
ApplicationUser removeUser = await _userManager.FindByIdAsync(model.Id.Clean());
if (removeUser != null)
{
identityResult = await _userManager.DeleteAsync(removeUser);
if (!identityResult.Succeeded) throw new Exception(identityResult.Errors.First().Description);
}
}
}
else
{
user = await _userManager.FindByIdAsync(model.Id.Clean());
if (user == null)
throw new KeyNotFoundException($"[Key]: {nameof(model.Id)} [Value]: {model.Id}");
}
user.DisplayName = model.DisplayName.Clean();
user.UserName = model.Email.Clean();
user.NormalizedUserName = model.Email.Clean();
user.Email = model.Email.Clean();
user.NormalizedEmail = model.Email.Clean();
identityResult = await _userManager.UpdateAsync(user);
if (!identityResult.Succeeded) throw new Exception(identityResult.Errors.First().Description);
if (!string.IsNullOrEmpty(model.ProviderUserId.Clean()))
{
var userLogins = await _userManager.GetLoginsAsync(user);
UserLoginInfo? userLogin = userLogins
.Where(x => x.LoginProvider == model.Provider.Clean())
.Where(x => x.ProviderKey == model.ProviderUserId.Clean())
.FirstOrDefault();
if (userLogin == null)
{
identityResult = await _userManager.AddLoginAsync(user, new UserLoginInfo(model.Provider.Clean(), model.ProviderUserId.Clean(), model.Provider.Clean()));
if (!identityResult.Succeeded) throw new Exception(identityResult.Errors.First().Description);
}
}
}
return View(model);
}

Get Claims in a WebApi Control from a JWT Token

This is my first time attempting to use Asp.Net Core Web Api. I have everything working including authentication and jwt token creation and verification. What I am trying to do is extract the user information that is in the token and use some of it when posting data to the database. I create the token like this:
public string NewToken(string ApiKey, ICAN_Context context)
{
var user = context.TblUserLogins.Where(x => x.ApiKey == ApiKey).FirstOrDefault();
int? CompanyId = context.TblEmployeeCompanies.Where(x => x.EmployeeId == user.EmployeeId).Select(x => x.CompanyId).FirstOrDefault();
var identity = new ClaimsIdentity();
identity.AddClaim(new Claim(ClaimTypes.Name, user.UserName));
identity.AddClaim(new Claim("CompanyId", CompanyId.ToString(), ClaimValueTypes.Integer32));
identity.AddClaim(new Claim("EmployeeCompanyId", user.EmployeeCompanyId.ToString(), ClaimValueTypes.Integer32 ));
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(identity),
Expires = DateTime.UtcNow.AddMinutes(60),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(secretKey), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
var jwtString = tokenHandler.WriteToken(token);
return jwtString;
}
I verify the token using a "filter":
public void OnAuthorization(AuthorizationFilterContext context)
{
var tokenManager = (ITokenManager)context.HttpContext.RequestServices.GetService(typeof(ITokenManager));
var result = true;
if(!context.HttpContext.Request.Headers.ContainsKey("Authorization"))
{
result = false;
}
string token = string.Empty;
if(result)
{
token = context.HttpContext.Request.Headers.First(x=>x.Key == "Authorization").Value;
try
{
var claimPrinciple = tokenManager.VerifyToken(token);
}
catch(Exception ex)
{
result = false;
context.ModelState.AddModelError("Unauthorized", ex.ToString());
}
}
if(!result)
{
context.Result = new UnauthorizedObjectResult(context.ModelState);
}
}
I have no problem retrieving the claims info from the token, my question is how do I get the claims from the token in my controller method?
I want to be able to retrieve its values in my methods something like this:
[HttpPost]
[Route("~/api/entity/department")]
public IActionResult CreateDepartment([FromBody] TblCompanyDepartmentsXlu department)
{
var identity = (ClaimsIdentity)User.Identity;
_context.Departments.Add(department);
_context.SaveChanges();
return Ok("Department created successfully!");
}
I also tried this from StackOverflow:
public static ClaimsPrincipal VerifyToken(string jwtToken)
{
TokenManager tokenManager = new TokenManager();
SecurityToken validatedToken;
TokenValidationParameters validationParameters = new TokenValidationParameters();
validationParameters.ValidateLifetime = true;
validationParameters.IssuerSigningKey = new Microsoft.IdentityModel.Tokens.SymmetricSecurityKey(tokenManager.secretKey);
validationParameters.ValidateAudience = false;
validationParameters.ValidateIssuer = false;
ClaimsPrincipal principal = new JwtSecurityTokenHandler().ValidateToken(jwtToken, validationParameters, out validatedToken);
return principal;
}
[HttpGet]
[Route("~/api/entity/GetEmployees")]
public List<TblEmployees> GetEmployees()
{
var identity = HttpContext.User.Identity as ClaimsIdentity;
if (identity != null)
{
IEnumerable<Claim> claims = identity.Claims;
}
var employees = _context.Employees.ToList();
return employees;
}
but identity.Claims is ALWAYS 0.
I am able to retrieve the claims right after verifying the token:
var claimPrinciple = TokenManager.VerifyToken(token);
I am able to retrieve the claims info here
var claims = claimPrinciple.Identities.First().Claims.ToList();
int? CompanyId = Convert.ToInt32(claims.Where(x => x.Type == "CompanyId").FirstOrDefault().Value);
int EmployeeCompanyId = Convert.ToInt32(claims.Where(x => x.Type == "EmployeeCompanyId").FirstOrDefault().Value);
But I am unable to retrieve them in the controller.
//startup.cs dotnet 6.0 :
builder.Services.AddHttpContextAccessor();
.
.
.
//where you want to use the IHttpContextAccessor :
//For example in user repository :
private readonly IHttpContextAccessor _httpContextAccessor;
public UserRepository(IHttpContextAccessor httpContextAccessor) =>
_httpContextAccessor = httpContextAccessor;
public void LogCurrentUser()
{
var username = _httpContextAccessor.HttpContext.User.Identity.Name;
// ...
}
#Klekmek was correct. All I needed to do was use the built-in JWT token functionality (which I didn't know existed). I removed the AuthenticationFilter and added this to my startup:
To the ConfigureServices section:
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(jwt => {
var key = Encoding.ASCII.GetBytes(Configuration["JwtConfig:Secret"]);
jwt.SaveToken = true;
jwt.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateLifetime = true,
ValidateIssuer = false,
ValidateAudience = false
};
});
And the Configure section:
app.UseAuthentication();
After adding that, this worked:
var identity = HttpContext.User.Identity as ClaimsIdentity;

Logout / Timeout redirect ASP.net Core

In my ASP.net core site, after 20 minutes of inactivity, the session expires, and on the next action that the user performs that requires authentication, the site redirects back to /Account/Login. The problem is, I'd like to redirect back to a different page depending on the session that was expired.
For example, people log in to my website at https://example.com/Account/Login?appId=12345. However, when the session expires and the user is redirected back to the login page, the appId=12345 is stripped. (12345 could be anything depending on the user). I'm not sure how to include a custom parameter in the redirect URL unique to each session.
Here's how I have the timeout currently configured in Startup.cs:
app.UseCookieAuthentication(new CookieAuthenticationOptions {
AutomaticAuthenticate = true,
AutomaticChallenge = true,
SlidingExpiration = true,
ExpireTimeSpan = TimeSpan.FromMinutes(20)
LoginPath = "/Account/Login"
});
I was asked for my httpget and httppost login methods, so here is a very stripped down version (especially the POST method) of them:
[RequireHttps]
[HttpGet]
public IActionResult Login(string returnUrl = "/", // The path to direct to after the login is complete
bool activated = false, // If the screen should prompt that the account has been activated. This flag is used from email activation only
string email = null, // To pre-populate an e-mail address in the username field
bool interactive = false, // Interactive login (ask for username first, then password OR create user)
string applicationId = null, // An application id (for tblApplications) to format the screen and log directly in to an application
string errorMessage = null
) {
LoginViewModel model = new LoginViewModel();
// Pre-set Username
model.EmailAddress = email;
// Application
Guid applicationGuid;
if (applicationId != null && Guid.TryParse(applicationId, out applicationGuid)) {
tblApplication application = dbo.GetSingle<tblApplication>(x => x.ApplicationGuid == applicationGuid);
if (application != null) {
model.applicationId = applicationId;
model.ApplicationTitle = application.Title;
List<tblSettings> settings = dbo.GetList<tblSettings>(x => x.ApplicationId == application.ApplicationId).ToList();
if (settings.Where(x => x.SettingMasterId == 41 && x.SettingValue1 == "1").Count() > 0) model.showHeader = false;
if (settings.Where(x => x.SettingMasterId == 42 && x.SettingValue1 == "1").Count() > 0) model.showFooter = false;
if (settings.Any(x => x.SettingMasterId == 52)) model.LoginHeader = settings.FirstOrDefault(x => x.SettingMasterId == 52).SettingValue1;
if (settings.Any(x => x.SettingMasterId == 63 && x.SettingValue1 != null)) model.themeId = int.Parse(settings.Where(x => x.SettingMasterId == 63 && x.SettingValue1 != null).FirstOrDefault().SettingValue1);
if (settings.Where(x => x.SettingMasterId == 67 && x.SettingValue1 == "1").Count() > 0) model.Interactive = true;
}
}
// Return URL after login successful
ViewData["ReturnUrl"] = returnUrl;
// Activation message from security provider
if (activated) model.activated = true;
// Interactive
if (interactive) model.Interactive = true;
// Error Message
if (errorMessage != null) model.BannerErrorMessage = errorMessage;
// Return to form
return View(model);
}
[HttpPost]
public async Task<IActionResult> Login(LoginViewModel vm, string returnUrl = null) {
if (ModelState.IsValid) {
tblIdentity identity = null;
try {
// Pull the client identity record from the database
identity = dbo.ExecuteQuery<tblIdentity>(x => x.ClientId == vm.EmailAddress).FirstOrDefault();
if (identity == null)
throw new Exception("Invalid Email or Password");
// ** Authentication Code here. On failure, throw exception
if (user.EmailVerified == null || user.EmailVerified == false) {
// ** Code for email address not verified
return RedirectToAction("AccountNotVerified", model);
}
else {
// Call the login procedure to create cookes
ClaimsPrincipal claimsPrincipal = await login(identity, user.UserId, user.FullName, user.Email, vm.applicationId);
// Sign user into cookie middleware
await HttpContext.Authentication.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, claimsPrincipal);
// Redirect back to the return url
return RedirectToLocal(returnUrl);
}
}
catch (Exception e) {
ModelState.AddModelError("", e.Message);
}
}
return View(vm);
}
public async Task<ClaimsPrincipal> login(tblClientIdentity identity, string userId, string fullName, string email, string applicationId = null) {
var claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity(new[] {
new Claim(ClaimTypes.NameIdentifier, userId),
new Claim(ClaimTypes.Name, fullName),
new Claim(ClaimTypes.Email, email)},
CookieAuthenticationDefaults.AuthenticationScheme));
// Application
if (string.IsNullOrWhiteSpace(applicationId) == false) {
Guid applicationGuid;
if (Guid.TryParse(applicationId, out applicationGuid)) {
tblApplication application = dbo.GetSingle<tblApplication>(x => x.ApplicationGuid == applicationGuid);
if (application != null) {
claimsPrincipal.Identities.First().AddClaim(new Claim("ApplicationId", applicationId, ClaimValueTypes.String, "https://example.com"));
HttpContext.Session.SetString("ApplicationId", applicationId);
}
else {
if (HttpContext != null) HttpContext.Session.Remove("ApplicationId");
RedirectToAction("Login", "Account", new { ErrorMessage = "The application could not be loaded" });
}
}
else {
if (HttpContext != null) HttpContext.Session.Remove("ApplicationId");
}
}
else {
if (HttpContext != null) HttpContext.Session.Remove("ApplicationId");
}
return claimsPrincipal;
}

Adding a SessionStore (ITicketStore) to my application cookie makes my Data Protection Provider fail to work

tl;dr
Have .NET Core 2.0 application which uses a Data Protection Provider which persists a key file across all of the sites on my domain.
Worked fine, however, application cookie became too big.
Implemented a SessionStore on the cookie using ITicketStore
Cookie size is greatly reduced, however, the key from the DPP no longer persists across my sites.
Is there something I'm supposed to do in my ITicketStore implementation to fix this? I'm assuming so, since this is where the problem arises, however, I could not figure it out.
Some snippets:
Startup.cs --> ConfigureServices()
var keysFolder = $#"c:\temp\_WebAppKeys\{_env.EnvironmentName.ToLower()}";
var protectionProvider = DataProtectionProvider.Create(new DirectoryInfo(keysFolder));
var dataProtector = protectionProvider.CreateProtector(
"Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware",
"Cookies",
"v2");
--snip--
services.AddSingleton<ITicketStore, TicketStore>();
--snip--
services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo(keysFolder))
.SetApplicationName("app_auth");
services.ConfigureApplicationCookie(options =>
{
options.Cookie.Name = ".XAUTH";
options.Cookie.Domain = ".domain.com";
options.ExpireTimeSpan = TimeSpan.FromDays(7);
options.LoginPath = "/Account/Login";
options.DataProtectionProvider = protectionProvider;
options.TicketDataFormat = new TicketDataFormat(dataProtector);
options.CookieManager = new ChunkingCookieManager();
options.SessionStore = services.BuildServiceProvider().GetService<ITicketStore>();
});
TicketStore.cs
public class TicketStore : ITicketStore
{
private IMemoryCache _cache;
private const string KeyPrefix = "AuthSessionStore-";
public TicketStore(IMemoryCache cache)
{
_cache = cache;
}
public Task RemoveAsync(string key)
{
_cache.Remove(key);
return Task.FromResult(0);
}
public Task RenewAsync(string key, AuthenticationTicket ticket)
{
var options = new MemoryCacheEntryOptions
{
Priority = CacheItemPriority.NeverRemove
};
var expiresUtc = ticket.Properties.ExpiresUtc;
if (expiresUtc.HasValue)
{
options.SetAbsoluteExpiration(expiresUtc.Value);
}
options.SetSlidingExpiration(TimeSpan.FromMinutes(60));
_cache.Set(key, ticket, options);
return Task.FromResult(0);
}
public Task<AuthenticationTicket> RetrieveAsync(string key)
{
AuthenticationTicket ticket;
_cache.TryGetValue(key, out ticket);
return Task.FromResult(ticket);
}
public async Task<string> StoreAsync(AuthenticationTicket ticket)
{
var key = KeyPrefix + Guid.NewGuid();
await RenewAsync(key, ticket);
return key;
}
I also ran into this issue.
The SessionIdClaim value in Microsoft.Owin.Security.Cookies is "Microsoft.Owin.Security.Cookies-SessionId", while the SessionIdClaim value in Microsoft.AspNetCore.Authentication.Cookies is "Microsoft.AspNetCore.Authentication.Cookies-SessionId".
This results in a SessionId Missing error due to this code on the AspNetCore side even when you implemented a distributed session store (using RedisCacheTicketStore for example) as decribed here: https://mikerussellnz.github.io/.NET-Core-Auth-Ticket-Redis/
I was able to re-compile the AspNetKatana project with the new string, and then the SessionID was found on the .NET Core side.
Additionally, it seems the AuthenticationTicket classes are different, so I was able to get this working by implementing a conversion method to convert the
Microsoft.Owin.Security.AuthenticationTicket Ticket to the Microsoft.AspNetCore.Authentication.AuthenticationTicket Ticket and then store the ticket using the AspNetCore serializer (Microsoft.AspNetCore.Authentication.TicketSerializer).
public Microsoft.AspNetCore.Authentication.AuthenticationTicket ConvertTicket(Microsoft.Owin.Security.AuthenticationTicket ticket)
{
Microsoft.AspNetCore.Authentication.AuthenticationProperties netCoreAuthProps = new Microsoft.AspNetCore.Authentication.AuthenticationProperties();
netCoreAuthProps.IssuedUtc = ticket.Properties.IssuedUtc;
netCoreAuthProps.ExpiresUtc = ticket.Properties.ExpiresUtc;
netCoreAuthProps.IsPersistent = ticket.Properties.IsPersistent;
netCoreAuthProps.AllowRefresh = ticket.Properties.AllowRefresh;
netCoreAuthProps.RedirectUri = ticket.Properties.RedirectUri;
ClaimsPrincipal cp = new ClaimsPrincipal(ticket.Identity);
Microsoft.AspNetCore.Authentication.AuthenticationTicket netCoreTicket = new Microsoft.AspNetCore.Authentication.AuthenticationTicket(cp, netCoreAuthProps, "Cookies");
return netCoreTicket;
}
private static Microsoft.AspNetCore.Authentication.TicketSerializer _netCoreSerializer = Microsoft.AspNetCore.Authentication.TicketSerializer.Default;
private static byte[] SerializeToBytesNetCore(Microsoft.AspNetCore.Authentication.AuthenticationTicket source)
{
return _netCoreSerializer.Serialize(source);
}
With these additional methods, the RenwAsync method can be changed to this:
public Task RenewAsync(string key, Microsoft.Owin.Security.AuthenticationTicket ticket)
{
var options = new DistributedCacheEntryOptions();
var expiresUtc = ticket.Properties.ExpiresUtc;
if (expiresUtc.HasValue)
{
options.SetAbsoluteExpiration(expiresUtc.Value);
}
var netCoreTicket = ConvertTicket(ticket);
// convert to .NET Core format
byte[] netCoreVal = SerializeToBytesNetCore(netCoreTicket);
// serialize ticket using .NET Core Serializer
_cache.Set(key, netCoreVal, options);
return Task.FromResult(0);
}
I am not sure if this is the best approach, but it seems to work on my test project, admittedly I am not using this in production, hopefully this helps.
UPDATE #1: Alternate approach to avoid re-compiling
It looks like this might also work by re-creating the cookie with both SessionId claim values on the OWIN side. This will allow you to use the standard library without re-compiling. I tried it this morning but have not had a chance to thoroughly test it, although on my initial test it does load the claims properly on both sides. Basically, if you modify the authentication ticket to have both SessionId claims, it will find the session in both applications. This code snippet gets the cookie, unprotects it, adds the additional claim, and then replaces the cookie inside the OnValidateIdentity event of the CookieAuthenticationProvider.
string cookieName = "myappname";
string KatanaSessionIdClaim = "Microsoft.Owin.Security.Cookies-SessionId";
string NetCoreSessionIdClaim = "Microsoft.AspNetCore.Authentication.Cookies-SessionId";
Microsoft.Owin.Security.Interop.ChunkingCookieManager cookieMgr = new ChunkingCookieManager();
OnValidateIdentity = ctx =>
{
var incomingIdentity = ctx.Identity;
var cookie = cookieMgr.GetRequestCookie(ctx.OwinContext, cookieName);
if (cookie != null)
{
var ticket = TicketDataFormat.Unprotect(cookie);
if (ticket != null)
{
Claim claim = ticket.Identity.Claims.FirstOrDefault(c => c.Type.Equals(KatanaSessionIdClaim));
Claim netCoreSessionClaim = ticket.Identity.Claims.FirstOrDefault(c => c.Type.Equals(NetCoreSessionIdClaim));
if (netCoreSessionClaim == null)
{
// adjust cookie options as needed.
CookieOptions opts = new CookieOptions();
opts.Expires = ticket.Properties.ExpiresUtc == null ?
DateTime.Now.AddDays(14) : ticket.Properties.ExpiresUtc.Value.DateTime;
opts.HttpOnly = true;
opts.Path = "/";
opts.Secure = true;
netCoreSessionClaim = new Claim(NetCoreSessionIdClaim, claim.Value);
ticket.Identity.AddClaim(netCoreSessionClaim);
string newCookieValue = TicketDataFormat.Protect(ticket);
cookieMgr.DeleteCookie(ctx.OwinContext, cookieName, opts);
cookieMgr.AppendResponseCookie(ctx.OwinContext, cookieName, newCookieValue, opts);
}
}
}
}
If there is a better approach I would be curious to know, or a better place to swap out the cookie.
The problem is, as other answers have pointed out, that the Owin cookie's session key claim has another type string than the one expected in ASP.Net Core.
The following implementation of a ticket data format makes sure to add the session key claim for ASP.Net Core when generating the cookie string.
public class AspNetCoreCompatibleTicketDataFormat : ISecureDataFormat<AuthenticationTicket> {
private const string OwinSessionIdClaim = "Microsoft.Owin.Security.Cookies-SessionId";
private const string AspNetCoreSessionIdClaim = "Microsoft.AspNetCore.Authentication.Cookies-SessionId";
private readonly ISecureDataFormat<AuthenticationTicket> dataFormat;
public AspNetCoreCompatibleTicketDataFormat(IDataProtector protector) {
this.dataFormat = new AspNetTicketDataFormat(protector);
}
public string Protect(AuthenticationTicket data) {
var sessionClaim = data.Identity.FindFirst(OwinSessionIdClaim);
if (sessionClaim != null) {
data.Identity.AddClaim(new Claim(AspNetCoreSessionIdClaim, sessionClaim.Value));
}
return this.dataFormat.Protect(data);
}
public AuthenticationTicket Unprotect(string protectedText) {
return this.dataFormat.Unprotect(protectedText);
}
}
This code should be added to the ASP.Net Framework project. You use it instead of the AspNetTicketDataFormat in your StartUp.cs code, like this:
app.UseCookieAuthentication(new CookieAuthenticationOptions {
TicketDataFormat = new AspNetCoreCompatibleTicketDataFormat(
new DataProtectorShim(...
The code makes sure that the generated cookie contains a session id claim known to ASP.NET Core. It works for the scenario where you generate the cookie in an ASP.NET Framework OWIN project and consume it in an ASP.NET Core project.
One would have to make sure to always add both to get it working in the opposite circumstance where the cookie is generated in the ASP.NET Core project.
I ended up doing a mix of the above answers, Replacing the ICookieManager implementation on the AspNetCore side that generates the cookies, adding both claims when doing so (as per the relevant part of the answer given by #AnthonyValeri):
public class OwinAspNetCompatibleCookieManager : ICookieManager
{
private const string OwinSessionIdClaim = "Microsoft.Owin.Security.Cookies-SessionId";
private const string AspNetCoreSessionIdClaim = "Microsoft.AspNetCore.Authentication.Cookies-SessionId";
private readonly ICookieManager actualCookieManager;
public OwinAspNetCompatibleCookieManager(ICookieManager actualCookieManager) => this.actualCookieManager = actualCookieManager;
// TODO oh this async void is so so bad, i have to find another way
public async void AppendResponseCookie(HttpContext context, string key, string value, CookieOptions options)
{
IAuthenticationHandler handler = await context.RequestServices.GetRequiredService<IAuthenticationHandlerProvider>().GetHandlerAsync(context, CookieAuthenticationDefaults.AuthenticationScheme).ConfigureAwait(false);
if (handler is CookieAuthenticationHandler cookieHandler)
{
value = MakeOwinAspNetCoreCompatible(key, value, cookieHandler.Options);
}
actualCookieManager.AppendResponseCookie(context, key, value, options);
}
public void DeleteCookie(HttpContext context, string key, CookieOptions options)
{
actualCookieManager.DeleteCookie(context, key, options);
}
public string GetRequestCookie(HttpContext context, string key)
{
return actualCookieManager.GetRequestCookie(context, key);
}
private string MakeOwinAspNetCoreCompatible(string key, string cookieValue, CookieAuthenticationOptions options)
{
if (key.Equals("MySharedCookieName") && !string.IsNullOrWhiteSpace(cookieValue))
{
AuthenticationTicket ticket = options.TicketDataFormat.Unprotect(cookieValue);
ClaimsPrincipal principal = ticket.Principal;
Claim aspNetCoreClaim = ticket.Principal.Claims.FirstOrDefault(x => x.Type.Equals(AspNetCoreSessionIdClaim));
Claim owinClaim = ticket.Principal.Claims.FirstOrDefault(x => x.Type.Equals(OwinSessionIdClaim));
Claim[] claims = null;
if (aspNetCoreClaim != null && owinClaim == null)
{
claims = new Claim[] { aspNetCoreClaim, new Claim(OwinSessionIdClaim, aspNetCoreClaim.Value) };
}
else if (aspNetCoreClaim == null && owinClaim != null)
{
claims = new Claim[] { owinClaim, new Claim(AspNetCoreSessionIdClaim, owinClaim.Value) };
}
if (claims?.Length > 0)
{
var newIdentity = new ClaimsIdentity(claims, principal.Identity.AuthenticationType);
principal = new ClaimsPrincipal(newIdentity);
ticket = new AuthenticationTicket(principal, ticket.AuthenticationScheme);
cookieValue = options.TicketDataFormat.Protect(ticket);
}
}
return cookieValue;
}
}
And then configuring it on the .AddCookie() call in ConfigureServices:
...
options.CookieManager = new OwinAspNetCompatibleCookieManager(new ChunkingCookieManager());
...
I ran into the same issue and banging my head to resolve this. But thanks to #Anthony Valeri to pointing right at where the issue is. So I came up with the solution below. (I was doing this as part of POC for one of our migration projects and this is not been tested in Production, but worked for POC.)
Created an extended CookieAuthenticationOptions class and added a new property.
public class ExtendedCookieAuthenticationOptions : CookieAuthenticationOptions
{
public string SessionIdClaim { get; set; }
}
Copied CookieAuthenticationHandler class from GitHub Source Code and extended that with above class
public class ExtendedCookieAuthenticationHandler : SignInAuthenticationHandler<ExtendedCookieAuthenticationOptions>
{
private const string HeaderValueNoCache = "no-cache";
private const string HeaderValueEpocDate = "Thu, 01 Jan 1970 00:00:00 GMT";
private const string SessionIdClaim = "Microsoft.AspNetCore.Authentication.Cookies-SessionId";
private bool _shouldRefresh;
private bool _signInCalled;
private bool _signOutCalled;
private DateTimeOffset? _refreshIssuedUtc;
private DateTimeOffset? _refreshExpiresUtc;
private string _sessionKey;
private Task<AuthenticateResult> _readCookieTask;
private AuthenticationTicket _refreshTicket;
public ExtendedCookieAuthenticationHandler(IOptionsMonitor<ExtendedCookieAuthenticationOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
: base(options, logger, encoder, clock)
{
}
/// <summary>
/// Added this to overwrite default SessionIdClaim value
/// </summary>
public virtual string SessionIdClaimType
{
get { return string.IsNullOrEmpty(Options.SessionIdClaim) ? SessionIdClaim : Options.SessionIdClaim; }
}
/// <summary>
/// The handler calls methods on the events which give the application control at certain points where processing is occurring.
/// If it is not provided a default instance is supplied which does nothing when the methods are called.
/// </summary>
protected new CookieAuthenticationEvents Events
{
get { return (CookieAuthenticationEvents)base.Events; }
set { base.Events = value; }
}
protected override Task InitializeHandlerAsync()
{
// Cookies needs to finish the response
Context.Response.OnStarting(FinishResponseAsync);
return Task.CompletedTask;
}
/// <summary>
/// Creates a new instance of the events instance.
/// </summary>
/// <returns>A new instance of the events instance.</returns>
protected override Task<object> CreateEventsAsync() => Task.FromResult<object>(new CookieAuthenticationEvents());
private Task<AuthenticateResult> EnsureCookieTicket()
{
// We only need to read the ticket once
if (_readCookieTask == null)
{
_readCookieTask = ReadCookieTicket();
}
return _readCookieTask;
}
private void CheckForRefresh(AuthenticationTicket ticket)
{
var currentUtc = Clock.UtcNow;
var issuedUtc = ticket.Properties.IssuedUtc;
var expiresUtc = ticket.Properties.ExpiresUtc;
var allowRefresh = ticket.Properties.AllowRefresh ?? true;
if (issuedUtc != null && expiresUtc != null && Options.SlidingExpiration && allowRefresh)
{
var timeElapsed = currentUtc.Subtract(issuedUtc.Value);
var timeRemaining = expiresUtc.Value.Subtract(currentUtc);
if (timeRemaining < timeElapsed)
{
RequestRefresh(ticket);
}
}
}
private void RequestRefresh(AuthenticationTicket ticket, ClaimsPrincipal replacedPrincipal = null)
{
var issuedUtc = ticket.Properties.IssuedUtc;
var expiresUtc = ticket.Properties.ExpiresUtc;
if (issuedUtc != null && expiresUtc != null)
{
_shouldRefresh = true;
var currentUtc = Clock.UtcNow;
_refreshIssuedUtc = currentUtc;
var timeSpan = expiresUtc.Value.Subtract(issuedUtc.Value);
_refreshExpiresUtc = currentUtc.Add(timeSpan);
_refreshTicket = CloneTicket(ticket, replacedPrincipal);
}
}
private AuthenticationTicket CloneTicket(AuthenticationTicket ticket, ClaimsPrincipal replacedPrincipal)
{
var principal = replacedPrincipal ?? ticket.Principal;
var newPrincipal = new ClaimsPrincipal();
foreach (var identity in principal.Identities)
{
newPrincipal.AddIdentity(identity.Clone());
}
var newProperties = new AuthenticationProperties();
foreach (var item in ticket.Properties.Items)
{
newProperties.Items[item.Key] = item.Value;
}
return new AuthenticationTicket(newPrincipal, newProperties, ticket.AuthenticationScheme);
}
private async Task<AuthenticateResult> ReadCookieTicket()
{
var cookie = Options.CookieManager.GetRequestCookie(Context, Options.Cookie.Name);
if (string.IsNullOrEmpty(cookie))
{
return AuthenticateResult.NoResult();
}
var ticket = Options.TicketDataFormat.Unprotect(cookie, GetTlsTokenBinding());
if (ticket == null)
{
return AuthenticateResult.Fail("Unprotect ticket failed");
}
if (Options.SessionStore != null)
{
var claim = ticket.Principal.Claims.FirstOrDefault(c => c.Type.Equals(SessionIdClaimType));
if (claim == null)
{
return AuthenticateResult.Fail("SessionId missing");
}
_sessionKey = claim.Value;
ticket = await Options.SessionStore.RetrieveAsync(_sessionKey);
if (ticket == null)
{
return AuthenticateResult.Fail("Identity missing in session store");
}
}
var currentUtc = Clock.UtcNow;
var expiresUtc = ticket.Properties.ExpiresUtc;
if (expiresUtc != null && expiresUtc.Value < currentUtc)
{
if (Options.SessionStore != null)
{
await Options.SessionStore.RemoveAsync(_sessionKey);
}
return AuthenticateResult.Fail("Ticket expired");
}
CheckForRefresh(ticket);
// Finally we have a valid ticket
return AuthenticateResult.Success(ticket);
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
var result = await EnsureCookieTicket();
if (!result.Succeeded)
{
return result;
}
var context = new CookieValidatePrincipalContext(Context, Scheme, Options, result.Ticket);
await Events.ValidatePrincipal(context);
if (context.Principal == null)
{
return AuthenticateResult.Fail("No principal.");
}
if (context.ShouldRenew)
{
RequestRefresh(result.Ticket, context.Principal);
}
return AuthenticateResult.Success(new AuthenticationTicket(context.Principal, context.Properties, Scheme.Name));
}
private CookieOptions BuildCookieOptions()
{
var cookieOptions = Options.Cookie.Build(Context);
// ignore the 'Expires' value as this will be computed elsewhere
cookieOptions.Expires = null;
return cookieOptions;
}
protected virtual async Task FinishResponseAsync()
{
// Only renew if requested, and neither sign in or sign out was called
if (!_shouldRefresh || _signInCalled || _signOutCalled)
{
return;
}
var ticket = _refreshTicket;
if (ticket != null)
{
var properties = ticket.Properties;
if (_refreshIssuedUtc.HasValue)
{
properties.IssuedUtc = _refreshIssuedUtc;
}
if (_refreshExpiresUtc.HasValue)
{
properties.ExpiresUtc = _refreshExpiresUtc;
}
if (Options.SessionStore != null && _sessionKey != null)
{
await Options.SessionStore.RenewAsync(_sessionKey, ticket);
var principal = new ClaimsPrincipal(
new ClaimsIdentity(
new[] { new Claim(SessionIdClaimType, _sessionKey, ClaimValueTypes.String, Options.ClaimsIssuer) },
Scheme.Name));
ticket = new AuthenticationTicket(principal, null, Scheme.Name);
}
var cookieValue = Options.TicketDataFormat.Protect(ticket, GetTlsTokenBinding());
var cookieOptions = BuildCookieOptions();
if (properties.IsPersistent && _refreshExpiresUtc.HasValue)
{
cookieOptions.Expires = _refreshExpiresUtc.Value.ToUniversalTime();
}
Options.CookieManager.AppendResponseCookie(
Context,
Options.Cookie.Name,
cookieValue,
cookieOptions);
await ApplyHeaders(shouldRedirectToReturnUrl: false, properties: properties);
}
}
protected async override Task HandleSignInAsync(ClaimsPrincipal user, AuthenticationProperties properties)
{
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
properties = properties ?? new AuthenticationProperties();
_signInCalled = true;
// Process the request cookie to initialize members like _sessionKey.
await EnsureCookieTicket();
var cookieOptions = BuildCookieOptions();
var signInContext = new CookieSigningInContext(
Context,
Scheme,
Options,
user,
properties,
cookieOptions);
DateTimeOffset issuedUtc;
if (signInContext.Properties.IssuedUtc.HasValue)
{
issuedUtc = signInContext.Properties.IssuedUtc.Value;
}
else
{
issuedUtc = Clock.UtcNow;
signInContext.Properties.IssuedUtc = issuedUtc;
}
if (!signInContext.Properties.ExpiresUtc.HasValue)
{
signInContext.Properties.ExpiresUtc = issuedUtc.Add(Options.ExpireTimeSpan);
}
await Events.SigningIn(signInContext);
if (signInContext.Properties.IsPersistent)
{
var expiresUtc = signInContext.Properties.ExpiresUtc ?? issuedUtc.Add(Options.ExpireTimeSpan);
signInContext.CookieOptions.Expires = expiresUtc.ToUniversalTime();
}
var ticket = new AuthenticationTicket(signInContext.Principal, signInContext.Properties, signInContext.Scheme.Name);
if (Options.SessionStore != null)
{
if (_sessionKey != null)
{
await Options.SessionStore.RemoveAsync(_sessionKey);
}
_sessionKey = await Options.SessionStore.StoreAsync(ticket);
var principal = new ClaimsPrincipal(
new ClaimsIdentity(
new[] { new Claim(SessionIdClaimType, _sessionKey, ClaimValueTypes.String, Options.ClaimsIssuer) },
Options.ClaimsIssuer));
ticket = new AuthenticationTicket(principal, null, Scheme.Name);
}
var cookieValue = Options.TicketDataFormat.Protect(ticket, GetTlsTokenBinding());
Options.CookieManager.AppendResponseCookie(
Context,
Options.Cookie.Name,
cookieValue,
signInContext.CookieOptions);
var signedInContext = new CookieSignedInContext(
Context,
Scheme,
signInContext.Principal,
signInContext.Properties,
Options);
await Events.SignedIn(signedInContext);
// Only redirect on the login path
var shouldRedirect = Options.LoginPath.HasValue && OriginalPath == Options.LoginPath;
await ApplyHeaders(shouldRedirect, signedInContext.Properties);
Logger.AuthenticationSchemeSignedIn(Scheme.Name);
}
protected async override Task HandleSignOutAsync(AuthenticationProperties properties)
{
properties = properties ?? new AuthenticationProperties();
_signOutCalled = true;
// Process the request cookie to initialize members like _sessionKey.
await EnsureCookieTicket();
var cookieOptions = BuildCookieOptions();
if (Options.SessionStore != null && _sessionKey != null)
{
await Options.SessionStore.RemoveAsync(_sessionKey);
}
var context = new CookieSigningOutContext(
Context,
Scheme,
Options,
properties,
cookieOptions);
await Events.SigningOut(context);
Options.CookieManager.DeleteCookie(
Context,
Options.Cookie.Name,
context.CookieOptions);
// Only redirect on the logout path
var shouldRedirect = Options.LogoutPath.HasValue && OriginalPath == Options.LogoutPath;
await ApplyHeaders(shouldRedirect, context.Properties);
Logger.AuthenticationSchemeSignedOut(Scheme.Name);
}
private async Task ApplyHeaders(bool shouldRedirectToReturnUrl, AuthenticationProperties properties)
{
Response.Headers[HeaderNames.CacheControl] = HeaderValueNoCache;
Response.Headers[HeaderNames.Pragma] = HeaderValueNoCache;
Response.Headers[HeaderNames.Expires] = HeaderValueEpocDate;
if (shouldRedirectToReturnUrl && Response.StatusCode == 200)
{
// set redirect uri in order:
// 1. properties.RedirectUri
// 2. query parameter ReturnUrlParameter
//
// Absolute uri is not allowed if it is from query string as query string is not
// a trusted source.
var redirectUri = properties.RedirectUri;
if (string.IsNullOrEmpty(redirectUri))
{
redirectUri = Request.Query[Options.ReturnUrlParameter];
if (string.IsNullOrEmpty(redirectUri) || !IsHostRelative(redirectUri))
{
redirectUri = null;
}
}
if (redirectUri != null)
{
await Events.RedirectToReturnUrl(
new RedirectContext<CookieAuthenticationOptions>(Context, Scheme, Options, properties, redirectUri));
}
}
}
private static bool IsHostRelative(string path)
{
if (string.IsNullOrEmpty(path))
{
return false;
}
if (path.Length == 1)
{
return path[0] == '/';
}
return path[0] == '/' && path[1] != '/' && path[1] != '\\';
}
protected override async Task HandleForbiddenAsync(AuthenticationProperties properties)
{
var returnUrl = properties.RedirectUri;
if (string.IsNullOrEmpty(returnUrl))
{
returnUrl = OriginalPathBase + OriginalPath + Request.QueryString;
}
var accessDeniedUri = Options.AccessDeniedPath + QueryString.Create(Options.ReturnUrlParameter, returnUrl);
var redirectContext = new RedirectContext<CookieAuthenticationOptions>(Context, Scheme, Options, properties, BuildRedirectUri(accessDeniedUri));
await Events.RedirectToAccessDenied(redirectContext);
}
protected override async Task HandleChallengeAsync(AuthenticationProperties properties)
{
var redirectUri = properties.RedirectUri;
if (string.IsNullOrEmpty(redirectUri))
{
redirectUri = OriginalPathBase + OriginalPath + Request.QueryString;
}
var loginUri = Options.LoginPath + QueryString.Create(Options.ReturnUrlParameter, redirectUri);
var redirectContext = new RedirectContext<CookieAuthenticationOptions>(Context, Scheme, Options, properties, BuildRedirectUri(loginUri));
await Events.RedirectToLogin(redirectContext);
}
private string GetTlsTokenBinding()
{
var binding = Context.Features.Get<ITlsTokenBindingFeature>()?.GetProvidedTokenBindingId();
return binding == null ? null : Convert.ToBase64String(binding);
}
}`
Replaced
private const string SessionIdClaim = "Microsoft.AspNetCore.Authentication.Cookies-SessionId";
with new property
/// <summary>
/// Added this to overwrite default SessionIdClaim value
/// </summary>
public virtual string SessionIdClaimType
{
get { return string.IsNullOrEmpty(Options.SessionIdClaim) ? SessionIdClaim : Options.SessionIdClaim; }
}
Added new extension method to use ExtendedCookieAuthenticationHandler.
public static class CookieExtentions
{
public static AuthenticationBuilder AddExtendedCookie(this AuthenticationBuilder builder, string authenticationScheme, string displayName, Action<ExtendedCookieAuthenticationOptions> configureOptions)
{
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<ExtendedCookieAuthenticationOptions>, PostConfigureCookieAuthenticationOptions>());
return builder.AddScheme<ExtendedCookieAuthenticationOptions, ExtendedCookieAuthenticationHandler>(authenticationScheme, displayName, configureOptions);
}
}
Used new extension method in ConfigureServices method in startup.cs
.AddExtendedCookie("AuthScheme", "DisplayName", options =>
{
options.Cookie.Name = "CookieName";
options.Cookie.Domain = ".domain.com";
options.Cookie.HttpOnly = true;
options.SlidingExpiration = true;
options.Events = new CookieAuthenticationEvents()
{
//Sample how to add additional check for logged in User at Application Level.
OnValidatePrincipal = async context => { await ValidateAsync(context); },
};
options.LoginPath = "/account/login";
options.CookieManager = new ChunkingCookieManager();
options.SessionIdClaim = "Microsoft.Owin.Security.Cookies-SessionId";
options.TicketDataFormat = ticketDataFormat;
//SessionStore is configured in PostConfigureCookieAuthenticationOptions with DI
//options.SessionStore = //From DI
});

Send Response using OWIN Middleware Web API

I am trying to customize access token json object in my Web APi 2 project.
Which I have done successfully. But I am unable to figure out the case of failed request.
Here is my custom response:-
{ data: {}, success: 0, message: "failed" }
And following is a GrantResourceOwnerCredentials methon in ApplicationOAuthProvider which inherits OAuthAuthorizationServerProvider
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
ApplicationUserManager userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
ApplicationUser user = userManager.Find(context.UserName, context.Password);
bool reqFailed = false;
string reqFailedMsg = "";
if (user == null)
{
reqFailed = true;
reqFailedMsg = "The user name or password is incorrect.";
}
if (!reqFailed && !user.EmailConfirmed)
{
reqFailed = true;
reqFailedMsg = "The user email is not confirmed.";
//context.SetError("invalid_grant", "The user email is not confirmed.");
//return;
}
if (!reqFailed && !user.IsActive)
{
reqFailed = true;
reqFailedMsg = "The user is disabled.";
//context.SetError("invalid_grant", "The user is disabled.");
//return;
}
if (reqFailed)
{
try
{
context.Response.Headers.Add(LMS.Utilities.Constants.MyMiddlewareHeader, new[] { (400).ToString() });
context.Response.Headers.Add(LMS.Utilities.Constants.MyMiddlewareHeaderMsg, new[] { reqFailedMsg });
}
catch (Exception ex)
{
}
return;
}
var form = await context.Request.ReadFormAsync();
if (form["deviceType"] != null && form["deviceToken"] != null)
{
user.DeviceToken = form["deviceToken"];
user.DeviceType = form["deviceType"];
userManager.Update(user);
}
ClaimsIdentity oAuthIdentity = userManager.GenerateUserIdentity(userManager, user,
OAuthDefaults.AuthenticationType);
ClaimsIdentity cookiesIdentity = userManager.GenerateUserIdentity(userManager, user,
CookieAuthenticationDefaults.AuthenticationType);
AuthenticationProperties properties = CreateProperties(user.UserName);
AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
context.Validated(ticket);
//context.Request.Context.Authentication.SignIn(cookiesIdentity);
context.Request.Context.Authentication.SignIn(properties, cookiesIdentity);
//return Task.FromResult<object>(null);
}
And middleware class:-
public class AuthenticationMiddleware : OwinMiddleware
{
public AuthenticationMiddleware(OwinMiddleware next) : base(next) {
}
public override async Task Invoke(IOwinContext context)
{
await Next.Invoke(context);
if (context.Response.Headers.ContainsKey(Constants.MyMiddlewareHeader))
{
using (var reader = new StreamReader(context.Request.Body))
{
var headerValues = context.Response.Headers.GetValues(Constants.MyMiddlewareHeader);
context.Response.StatusCode = Convert.ToInt16(headerValues.FirstOrDefault());
context.Response.Headers.Remove(Constants.MyMiddlewareHeader);
var headerValuesMessage = context.Response.Headers.GetValues(Constants.MyMiddlewareHeaderMsg);
string msg = headerValuesMessage.FirstOrDefault().ToString();
context.Response.Headers.Remove(Constants.MyMiddlewareHeaderMsg);
string resp = Newtonsoft.Json.JsonConvert.SerializeObject(new ResponseViewModel<object>(new { }, (int)ResponseStatus.Fail, msg));
var response = context.Response;
var body = await reader.ReadToEndAsync();
var bytes = Encoding.UTF8.GetBytes(resp);
response.ContentLength = bytes.Length;
await response.WriteAsync(bytes);
return;
}
}
}
}
What I am getting in respose is:-
{"error":"invalid_grant"}{"Data":{},"Status":0,"Message":"The user name or
Which is invalid json as you can see.
Any help would be appreciated.
Thanks!

Resources