Web Api with Owin with JWT always fails to authorize request - oauth-2.0

I have followed the tutorials up till this point in the series. I am using one project in the solution that acts as both the token issuing authority as well as the resource server.
The JWT is generated using the endpoint mentioned in the startup class and I validated it on jwt.io as well. However when I pass this JWT using Postman on Chrome to the resource API end point secured with an Authorize attribute, I always find it returning
{
"message": "Authorization has been denied for this request." }
The other api method as in the api controller class below works when called thru Postman on Chrome.
I have used the latest versions of all dlls required from the nuget console
Code in the startup class
public void Configuration(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
ConfigureOAuthTokenGeneration(app);
ConfigureOAuthTokenConsumption(app);
WebApiConfig.Register(config);
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
app.UseWebApi(config);
}
private void ConfigureOAuthTokenGeneration(IAppBuilder app)
{
OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
//For Dev enviroment only (on production should be AllowInsecureHttp = false)
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/oauth/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = new CustomOAuthProvider(),
AccessTokenFormat = new CustomJwtFormat(ConfigurationManager.AppSettings["Issuer"]),
};
// OAuth 2.0 Bearer Access Token Generation
app.UseOAuthAuthorizationServer(OAuthServerOptions);
}
private void ConfigureOAuthTokenConsumption(IAppBuilder app)
{
string issuer = ConfigurationManager.AppSettings["Issuer"];
string audienceId = ConfigurationManager.AppSettings["AudienceId"];
byte[] audienceSecret = TextEncodings.Base64Url.Decode(ConfigurationManager.AppSettings["AudienceSecret"]);
// Api controllers with an [Authorize] attribute will be validated with JWT
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
AllowedAudiences = new[] { audienceId },
IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
{
new SymmetricKeyIssuerSecurityTokenProvider(issuer, audienceSecret)
}
});
}
Code in the Custom OAuthProvider
public class CustomOAuthProvider : OAuthAuthorizationServerProvider
{
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated();
return Task.FromResult<object>(null);
}
public override Task MatchEndpoint(OAuthMatchEndpointContext context)
{
//avoid pre-flight calls
if (context.OwinContext.Request.Method == "OPTIONS" && context.IsTokenEndpoint)
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Methods", new[] { "POST" });
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Headers", new[] { "accept", "authorization", "content-type" });
context.OwinContext.Response.StatusCode = 200;
context.RequestCompleted();
return Task.FromResult<object>(null);
}
return base.MatchEndpoint(context);
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
//setting up claims in the constructor of class UserDetails
UserDetails user = new UserDetails();
user.UserName = context.UserName;
user.FirstName = "Dummy First";
user.LastName = "Dummy Last";
ClaimsIdentity identity = new ClaimsIdentity("JWT-BearerAuth-Test");
identity.AddClaim(new Claim(ClaimTypes.Name, context.UserName));
foreach (string claim in user.Claims)
{
identity.AddClaim(new Claim(ClaimTypes.Role, claim));
}
var ticket = new AuthenticationTicket(identity, null);
context.Validated(ticket);
}
}
The custom JWT class
public class CustomJwtFormat : ISecureDataFormat<AuthenticationTicket>
{
private readonly string _issuer = string.Empty;
public CustomJwtFormat(string issuer)
{
_issuer = issuer;
}
public string Protect(AuthenticationTicket data)
{
if (data == null)
{
throw new ArgumentNullException("data");
}
string audienceId = ConfigurationManager.AppSettings["AudienceId"];
string symmetricKeyAsBase64 = ConfigurationManager.AppSettings["AudienceSecret"];
var keyByteArray = TextEncodings.Base64Url.Decode(symmetricKeyAsBase64);
var signingKey = new HmacSigningCredentials(keyByteArray);
var issued = data.Properties.IssuedUtc;
var expires = data.Properties.ExpiresUtc;
var token = new JwtSecurityToken(_issuer, audienceId, data.Identity.Claims, issued.Value.UtcDateTime, expires.Value.UtcDateTime, signingKey);
var handler = new JwtSecurityTokenHandler();
var jwt = handler.WriteToken(token);
return jwt;
}
}
The Resource server's Api controller
public class AdminController : ApiController
{
//This call works
public IHttpActionResult ReadData(string id)
{
return Ok("ID sent in:" + id);
}
//[Authorize(Roles="EditRecord")] //doesnt work
[Authorize] //doesnt work either
public IHttpActionResult EditData(string id)
{
return Ok("Edited ID:" + id);
}
}
My environment is VS2013 with Framework 4.5 using OAuth2 with Web Api 2. Please excuse the long post.

You need to make sure that values for issuer, audienceId, and audienceSecret used in method "ConfigureOAuthTokenConsumption" are the same values used when you generated the JWT token, take care of trailing slashes "/".
This is the only thing comes to my mind right now.

Related

storing JWT token in cookie in MVC 5

I wanted to make JWT auth in my MVC app. I make Authorization web service in Web API which returns token correctly. After that Im trying to store token in cookie.
[HttpPost]
public async Task<ActionResult> Login(LoginDto loginDto)
{
var token = await loginService.GetToken(loginDto);
if (!string.IsNullOrEmpty(token))
{
var cookie = new System.Web.HttpCookie("token", token)
{
HttpOnly = true
};
Response.Cookies.Add(cookie);
return RedirectToAction("Index", "Product");
}
return View("LoginFailed");
}
But now I wanted to add this token to headers for every request. So I decided action filters would be best for achieve this.
public class CustomActionFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var token = filterContext.HttpContext.Request.Cookies.Get("token");
if (token != null)
filterContext.HttpContext.Request.Headers.Add("Authorization", $"Bearer {token}");
base.OnActionExecuting(filterContext);
}
}
Startup
public class Startup
{
public void Configuration(IAppBuilder app)
{
AutofacConfig.Configure();
AreaRegistration.RegisterAllAreas();
RouteConfig.RegisterRoutes(RouteTable.Routes);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
ConfigureOAuth(app);
}
public void ConfigureOAuth(IAppBuilder app)
{
var issuer = System.Configuration.ConfigurationManager.AppSettings["issuer"];
var audience = System.Configuration.ConfigurationManager.AppSettings["appId"];
var secret = TextEncodings.Base64Url.Decode(System.Configuration.ConfigurationManager.AppSettings["secret"]);
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
AllowedAudiences = new[] { audience },
IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
{
new SymmetricKeyIssuerSecurityTokenProvider(issuer, secret)
},
});
}
}
And then I just marked controller which authorize attribute. It works fine when I called it with POSTMAN.
But action filters in MVC are fired always after authorization filter. So I have questions:
How to add token from cookie to every request ? Is it good practice? If not what I should do?
How about csrf attacks and others ? Is AntiForgeryTokenAttr will do the work? What about ajax calls then?
Additional Information
This is how login service looks like. Its just making call to auth endpoint.
public class LoginService : ILoginService
{
public async Task<string> GetToken(LoginDto loginDto)
{
var tokenIssuer = ConfigurationManager.AppSettings["issuer"];
using (var httpClient = new HttpClient {BaseAddress = new Uri($"{tokenIssuer}/oauth2/token")})
{
using (var response = await httpClient.PostAsync(httpClient.BaseAddress, new FormUrlEncodedContent(
new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("username", loginDto.Username),
new KeyValuePair<string, string>("password", loginDto.Password),
new KeyValuePair<string, string>("grant_type", "password"),
new KeyValuePair<string, string>("client_id", ConfigurationManager.AppSettings["appId"])
})))
{
var contents = await response.Content.ReadAsStringAsync();
if (response.StatusCode == HttpStatusCode.OK)
{
var deserializedResponse =
new JavaScriptSerializer().Deserialize<Dictionary<string, string>>(contents);
var token = deserializedResponse["access_token"];
return token;
}
}
return null;
}
}
}
I found a solution. I just make custom OAuthBearerAuthenticationProvider provider and inside this class Im retrieve token from cookie and then assign this to context.Token
public class MvcJwtAuthProvider : OAuthBearerAuthenticationProvider
{
public override Task RequestToken(OAuthRequestTokenContext context)
{
var token = context.Request.Cookies.SingleOrDefault(x => x.Key == "token").Value;
context.Token = token;
return base.RequestToken(context);
}
}
And then inside startup.cs
public class Startup
{
public void Configuration(IAppBuilder app)
{
AutofacConfig.Configure();
AreaRegistration.RegisterAllAreas();
RouteConfig.RegisterRoutes(RouteTable.Routes);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
ConfigureOAuth(app);
}
public void ConfigureOAuth(IAppBuilder app)
{
var issuer = System.Configuration.ConfigurationManager.AppSettings["issuer"];
var audience = System.Configuration.ConfigurationManager.AppSettings["appId"];
var secret = TextEncodings.Base64Url.Decode(System.Configuration.ConfigurationManager.AppSettings["secret"]);
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
AllowedAudiences = new[] { audience },
IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
{
new SymmetricKeyIssuerSecurityTokenProvider(issuer, secret)
},
Provider = new MvcJwtAuthProvider() // override custom auth
});
}
}

OWIN not working on live server

I am using OWIN to implement OAUTH2 in my MVC WEB API application.
I have setup the startup like this
public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }
static Startup()
{
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/token"),
Provider = new CustomOAuthProvider(),
RefreshTokenProvider = new CustomOAuthRefreshProvider(),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(180),
AllowInsecureHttp = true
};
}
public void ConfigureAuth(IAppBuilder app)
{
app.UseOAuthBearerTokens(OAuthOptions);
}
The CustomOAuthProvider Class contains
public override Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
return Task.Factory.StartNew(() =>
{
var username = context.UserName;
var password = context.Password;
var userService = new Merchants();
MerchantsModel user = userService.Authenticate(username, password);
if (user.MerchantId > 0)
{
var claims = new List<Claim>()
{
new Claim(ClaimTypes.Name, user.MerchantName),
new Claim(ClaimTypes.Email, user.EmailId),
new Claim("CompanyName", user.CompanyName),
new Claim("UserID", user.MerchantId.ToString()),
new Claim("CreatedOn", DateTime.UtcNow.ToString("dd-MMM-yyyy HH:mm:ss"))
};
ClaimsIdentity oAutIdentity = new ClaimsIdentity(claims, Startup.OAuthOptions.AuthenticationType);
context.Validated(new AuthenticationTicket(oAutIdentity, new AuthenticationProperties() { }));
}
else
{
context.SetError("Invalid Username or Password", "Error");
}
});
}
Now when i test this using POSTMAN it works perfectly on my local server and generates the access_token.
But when i deploy it on the web server it returns the error
{
"error": "unsupported_grant_type"
}
Am i missing something here?
this is the first time i am implementing OAUTH2 in my application.
Do i need to add some more code of config to make it work on the live server.
P.S. The request i send for authenticating is same in both cases:
grant_type=password&username=myusername&password=mypassword

app.UseIdentityServerBearerTokenAuthentication and UserInfo

For my WebAPI, I'm using:
public void ConfigureAuth( IAppBuilder app )
{
app.UseIdentityServerBearerTokenAuthentication( new IdentityServerBearerTokenAuthenticationOptions
{
Authority = ConfigurationManager.AppSettings[ "ida:Authority" ],
RequiredScopes = new[ ]
{
"XXXXAPI"
}
} );
}
I authenticate okay, but I need to get my roles and other information supplied by the UserInfo endpoint.
Does UseIdentityServerBearerTokenAuthentication do this automatically or is there an event like OpenIdConnectAuthenticationNotifications.AuthroizationCodeReceived that I should be using to set the ClaimsIdentity?
app.UseIdentityServerBearerTokenAuthentication does set the roles and scopes automatically.
For some unkown reason, I only returned data from the GetProfileDataAsync of the UserService only when the caller was UserInfoEndpoint. Once I got rid of this piece of code, all the roles were automatically populated.
You must use OAuth2+JWT and some custom configuration to save user roles and other claims in access token.
Add these values in Web.config
<appSettings>
<add key="as:AudienceId" value="414e1927a3884f68abc79f7283837fd1" />
<add key="as:AudienceSecret" value="qMCdFDQuF23RV1Y-1Gq9L3cF3VmuFwVbam4fMTdAfpo" />
</appSettings>
Write a CustomJwtFormat class
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.DataHandler.Encoder;
using System;
using System.Configuration;
using System.IdentityModel.Tokens;
using Thinktecture.IdentityModel.Tokens;
public class CustomJwtFormat : ISecureDataFormat<AuthenticationTicket>
{
private readonly string _issuer = string.Empty;
public CustomJwtFormat(string issuer)
{
_issuer = issuer;
}
public string Protect(AuthenticationTicket data)
{
if (data == null)
{
throw new ArgumentNullException("data");
}
string audienceId = ConfigurationManager.AppSettings["as:AudienceId"];
string symmetricKeyAsBase64 = ConfigurationManager.AppSettings["as:AudienceSecret"];
var keyByteArray = TextEncodings.Base64Url.Decode(symmetricKeyAsBase64);
var signingKey = new HmacSigningCredentials(keyByteArray);
var issued = data.Properties.IssuedUtc;
var expires = data.Properties.ExpiresUtc;
var token = new JwtSecurityToken(_issuer, audienceId, data.Identity.Claims, issued.Value.UtcDateTime, expires.Value.UtcDateTime, signingKey);
var handler = new JwtSecurityTokenHandler();
var jwt = handler.WriteToken(token);
return jwt;
}
}
Create a custom authenticationProvider
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.OAuth;
public class ApplicationOAuthProvider : OAuthAuthorizationServerProvider
{
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated();
return Task.FromResult<object>(null);
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
using (AuthRepository _repo = new AuthRepository())
{
User user = await _repo.FindUser(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
}
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim("unique_name", context.UserName));
identity.AddClaim(new Claim("role", "user"));
context.Validated(identity);
}
}
Configure Your Custom Setting
private static void ConfigureOAuthTokenGeneration(IAppBuilder app)
{
OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
//For Dev enviroment only (on production should be AllowInsecureHttp = false)
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/oauth/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = new ApplicationOAuthProvider(),
AccessTokenFormat = new CustomJwtFormat("http://localhost/")
};
// OAuth 2.0 Bearer Access Token Generation
app.UseOAuthAuthorizationServer(OAuthServerOptions);
}
private static void ConfigureOAuthTokenConsumption(IAppBuilder app)
{
var issuer = "http://localhost/";
string audienceId = ConfigurationManager.AppSettings["as:AudienceId"];
byte[] audienceSecret = TextEncodings.Base64Url.Decode(ConfigurationManager.AppSettings["as:AudienceSecret"]);
// Api controllers with an [Authorize] attribute will be validated with JWT
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
AllowedAudiences = new[] { audienceId },
IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
{
new SymmetricKeyIssuerSecurityTokenProvider(issuer, audienceSecret)
}
});
}
public void Configuration(IAppBuilder app(
{
app.UseAutofacMvc();
ConfigureOAuthTokenGeneration(app);
ConfigureOAuthTokenConsumption(app);
//other codes
}
If you want to get userInfo from access token, do it as follows:
public static string GetUserNameFromOAuth(HttpRequestMessage Request)
{
if (Request.Headers.Contains("Authorization"))
{
var authHeader = Request.Headers.GetValues("Authorization");
var authEncoded = authHeader.FirstOrDefault();
var authList = authEncoded.Split(' ');
var payload = authList[1];
var symmetricKeyAsBase64 = ConfigurationManager.AppSettings["as:AudienceSecret"];
var keyByteArray = TextEncodings.Base64Url.Decode(symmetricKeyAsBase64);
var signingKey = new HmacSigningCredentials(keyByteArray);
string token = JWT.JsonWebToken.Decode(payload, keyByteArray);
var jsonObject = JsonConvert.DeserializeObject<Dictionary<string, dynamic>>(token);
var userName = jsonObject.FirstOrDefault(p => p.Key == "unique_name").Value;
return userName;
}
return "";
}
Test in postman:

OAuth token bearer extra user information

I am using Web API secured with OAuth Bearer token. When getting the token I want to send extra information to the user, so I tried the following as per this thread:
CustomOAuthProvider.cs:
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
// Other stuff, cut off for brevity
var user = await userManager.FindAsync(context.UserName, context.Password);
ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(userManager, "JWT");
oAuthIdentity.AddClaims(ExtendedClaimsProvider.GetClaims(user));
oAuthIdentity.AddClaims(RolesFromClaims.CreateRolesBasedOnClaims(oAuthIdentity));
var ticket = new AuthenticationTicket(oAuthIdentity, this.CreateProperties(user.UserName, oAuthIdentity));
context.Validated(ticket);
}
private AuthenticationProperties CreateProperties(string userName, ClaimsIdentity oAuthIdentity)
{
var data = new Dictionary<string, string>
{
{ "username", userName },
{ "roles", JsonConvert.SerializeObject(oAuthIdentity.Claims.Where(c=> c.Type == ClaimTypes.Role).Select(c => c.Value).ToArray()) }
};
return new AuthenticationProperties(data);
}
But the returned object is always as following:
{
access_token: "theTokenHash"
expires_in: 86399
token_type: "bearer"
}
This is my Startup.cs:
public void Configuration(IAppBuilder app)
{
// AutoMapper
AutoMapperConfig.RegisterMappings();
var httpConfig = new HttpConfiguration();
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
ConfigureOAuthTokenGeneration(app);
ConfigureOAuthTokenConsumption(app);
ConfigureWebApi(httpConfig);
WebApiConfig.Register(httpConfig);
AutofacConfig.Register(httpConfig);
app.UseWebApi(httpConfig);
httpConfig.EnsureInitialized();
}
private void ConfigureOAuthTokenGeneration(IAppBuilder app)
{
// Configure the db context and user manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationRoleManager>(ApplicationRoleManager.Create);
var OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
//For Dev enviroment only (on production should be AllowInsecureHttp = false)
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/oauth/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = new CustomOAuthProvider(),
AccessTokenFormat = new CustomJwtFormat("http://localhost:59822")
};
// OAuth 2.0 Bearer Access Token Generation
app.UseOAuthAuthorizationServer(OAuthServerOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}
What am I doing wrong here?
Wow never mind, I dug into the full example given in the answer of the link. It seems that adding the extra fields isn't enough. You still have to add the parameters to the context yourself by overriding the TokenEndpoint function:
public override Task TokenEndpoint(OAuthTokenEndpointContext context)
{
foreach (KeyValuePair<string, string> property in context.Properties.Dictionary)
{
context.AdditionalResponseParameters.Add(property.Key, property.Value);
}
return Task.FromResult<object>(null);
}

Getting Access_Denied response when using Owin.Security.Providers.LinkedIn.LinkedInAuthenticationProvider

I'm playing with Owin.Security.Providers project to allow my MVC 5 app sign in with various social networks. I created an app in LinkedIn and set its scope to r_emailaddress and r_basicprofile. When I try to sign in with LinkedIn accout, I get /Login/SigninRedirect?error=access_denied on my return URL. Though my CallbackPath which is set to /signin-redirect URL has code and state attributes. In Fiddler it looks like this: /signin-redirect?code=<code_id>&state=<state_id>.
Anyone has any ideas what I might be doing wrong?
Here is my code:
protected virtual void ConfigureAuth(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
var linkedInAuthOptions = new LinkedInAuthenticationOptions()
{
ClientId = <client_id>,
ClientSecret = <client_secret>,
CallbackPath = new PathString("/signin-redirect")
};
app.UseLinkedInAuthentication(linkedInAuthOptions);
}
In my Login controller I handle sign in and the redirect similar to VS2013 example:
public class LoginController : Controller
{
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult ExternalLogin(string provider, string returnUrl)
{
var redirectUrl = "/Login/SigninRedirect";
redirectUrl += string.IsNullOrEmpty(returnUrl) ? "" : "?redirectUrl=" + returnUrl;
return new ChallengeResult(provider, redirectUrl);
}
// Redirects from successful social signin to a speccified URL.
public ActionResult SigninRedirect(string redirectUrl)
{
var redirect = string.IsNullOrEmpty(redirectUrl) ? "/" : redirectUrl;
ExternalLoginInfo loginInfo = AuthenticationManager.GetExternalLoginInfo();
// loginInfo is null when trying to sign in with LinkedIn
// works fine for Twitter though
if (loginInfo != null)
{
var authResult = AuthenticationManager.AuthenticateAsync(DefaultAuthenticationTypes.ExternalCookie).Result;
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
var claims = authResult.Identity.Claims.ToList();
claims.Add(new Claim(ClaimTypes.AuthenticationMethod, loginInfo.Login.LoginProvider));
var claimsIdentity = new ClaimsIdentity(claims, DefaultAuthenticationTypes.ExternalCookie);
AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = true }, claimsIdentity);
}
return new RedirectResult(redirect);
}
#region Helpers
private IAuthenticationManager AuthenticationManager
{
get
{
return HttpContext.GetOwinContext().Authentication;
}
}
private class ChallengeResult : HttpUnauthorizedResult
{
private const string XsrfKey = "XsrfId";
public ChallengeResult(string provider, string redirectUrl, string userId = null)
{
LoginProvider = provider;
RedirectUrl = redirectUrl;
UserId = userId;
}
public string UserId { get; set; }
public string RedirectUrl { get; set; }
public string LoginProvider { get; set; }
public override void ExecuteResult(ControllerContext context)
{
var properties = new AuthenticationProperties() {RedirectUri = RedirectUrl};
if (UserId != null)
{
properties.Dictionary[XsrfKey] = UserId;
}
context.HttpContext.GetOwinContext().Authentication.Challenge(properties, LoginProvider);
}
}
#endregion
}
The same code works fine when I try to sign in with Twitter account.
Found the issue. The problem was that I registered the same CallbackPath (/signin-redirect) for both Twitter and LinkedIn providers. Like this:
var twitterAuthOptions = new TwitterAuthenticationOptions()
{
ConsumerKey = <consumerKey>,
ConsumerSecret = <consumerSecret>,
CallbackPath = new PathString("/signin-redirect")
};
app.UseTwitterAuthentication(twitterAuthOptions);
var linkedInAuthOptions = new LinkedInAuthenticationOptions()
{
ClientId = <clientId>,
ClientSecret = <clientSecret>,
CallbackPath = new PathString("/signin-redirect")
};
app.UseLinkedInAuthentication(linkedInAuthOptions);
When I set different CallbackPath's, everything started working fine.

Resources