JWT BearerHandler Token Falied but request is still processed - jwt

I have run into a very strange problem, and I am guessing that I am missing something in my setup.
I have an WebAPI that is secured by an IdentityServer4. It's only using Client_credentials. If i write the wrong ClientId och ClientSecret that user is not Authenticated, and I can't connect to my WebAPI. But if I write the wrong scope name the request is still processed and I get my response back, the strange part is that an exception is thrown, but for some reason it's ignored by the .NET Core Framework.
Here are some debug info from my output window.
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request starting HTTP/1.1 GET https://localhost:44360/v1/bookings
Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler:Information: Failed to validate the token.
Microsoft.IdentityModel.Tokens.SecurityTokenInvalidAudienceException: IDX10214: Audience validation failed. Audiences: '[PII is hidden]'. Did not match: validationParameters.ValidAudience: '[PII is hidden]' or validationParameters.ValidAudiences: '[PII is hidden]'.
at Microsoft.IdentityModel.Tokens.Validators.ValidateAudience(IEnumerable`1 audiences, SecurityToken securityToken, TokenValidationParameters validationParameters)
at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateAudience(IEnumerable`1 audiences, JwtSecurityToken jwtToken, TokenValidationParameters validationParameters)
at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateTokenPayload(JwtSecurityToken jwtToken, TokenValidationParameters validationParameters)
at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateToken(String token, TokenValidationParameters validationParameters, SecurityToken& validatedToken)
at Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler.HandleAuthenticateAsync()
Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler:Information: Bearer was not authenticated. Failure message: IDX10214: Audience validation failed. Audiences: '[PII is hidden]'. Did not match: validationParameters.ValidAudience: '[PII is hidden]' or validationParameters.ValidAudiences: '[PII is hidden]'.
Microsoft.AspNetCore.Routing.EndpointMiddleware:Information: Executing endpoint 'TRS.BookingService.Api.Controllers.BookingsController.Get (TRS.BookingService.Api)'
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Route matched with {action = "Get", controller = "Bookings"}. Executing controller action with signature System.Threading.Tasks.Task`1[Microsoft.AspNetCore.Mvc.ActionResult`1[System.Collections.Generic.IEnumerable`1[System.String]]] Get() on controller TRS.BookingService.Api.Controllers.BookingsController (TRS.BookingService.Api).
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Executing action method TRS.BookingService.Api.Controllers.BookingsController.Get (TRS.BookingService.Api) - Validation state: Valid
TRS.BookingService.Api.Controllers.BookingsController:Information: Getting all bookings
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Executed action method TRS.BookingService.Api.Controllers.BookingsController.Get (TRS.BookingService.Api), returned result Microsoft.AspNetCore.Mvc.ObjectResult in 96.2159ms.
Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor:Information: Executing ObjectResult, writing value of type 'System.String[]'.
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Executed action TRS.BookingService.Api.Controllers.BookingsController.Get (TRS.BookingService.Api) in 280.2344ms
Microsoft.AspNetCore.Routing.EndpointMiddleware:Information: Executed endpoint 'TRS.BookingService.Api.Controllers.BookingsController.Get (TRS.BookingService.Api)'
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request finished in 1345.3829ms 200 application/json; charset=utf-8
So even that there is an exception thrown that says that the token isn't validated the request is still allowed to continue and execute and the response is sent back to the client.
This is how the ConfigureServices looks like:
services
.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.Authority = "https://localhost:44392/";
options.Audience = "FAKE_SCOPE";
});
And the Configure() Methods
app.UseAuthentication();
app.UseMvc();
This is how the JWT Token looks like:
{
"nbf": 1562062882,
"exp": 1562066482,
"iss": "https://localhost:44392",
"aud": [
"https://localhost:44392/resources",
"bookingApi"
],
"client_id": "clientId",
"scope": [
"bookingApi"
]
}
And this is the Client code calling the API.
var idpUrl = "https://localhost:44392/";
var clientId = "clientId";
var clientSecret = "secret";
var scope = "bookingApi";
var accessToken = await GetAccessTokenAsync(new Uri(idpUrl), clientId, clientSecret, scope);
string content = await GetContent(new Uri("https://localhost:44360/v1/bookings"), accessToken);
I guess I have missed something when it comes to Authorization, I have tried different
services.Authorization()
In the ConfigureServices() methods but it doesn't help, guess I have written it wrong.
Best Regards
Magnus

I came across this article after hitting the same issue. After much banging of head I discovered that it was caused in my case by using services.AddMvcCore() (with .AddJsonFormatters().AddDataAnnotations() in my case) rather than services.AddMvc(). Only with .AddMvcCore() do I get a 401 for token validation failure.
Seems that you need to add .AddAuthorization() to the mix when using .AddMvcCore as it is not added by default. Without it, the token validation fails, but the request pipeline continues quite happily.

After spending a day trying to figure out why it's not working I decided to step-thru the Microsoft code and found this in the AuthenticationMiddleware.
public class AuthenticationMiddleware
{
private readonly RequestDelegate _next;
public AuthenticationMiddleware(RequestDelegate next, IAuthenticationSchemeProvider schemes)
{
if (next == null)
{
throw new ArgumentNullException(nameof(next));
}
if (schemes == null)
{
throw new ArgumentNullException(nameof(schemes));
}
_next = next;
Schemes = schemes;
}
public IAuthenticationSchemeProvider Schemes { get; set; }
public async Task Invoke(HttpContext context)
{
context.Features.Set<IAuthenticationFeature>(new AuthenticationFeature
{
OriginalPath = context.Request.Path,
OriginalPathBase = context.Request.PathBase
});
// Give any IAuthenticationRequestHandler schemes a chance to handle the request
var handlers = context.RequestServices.GetRequiredService<IAuthenticationHandlerProvider>();
foreach (var scheme in await Schemes.GetRequestHandlerSchemesAsync())
{
var handler = await handlers.GetHandlerAsync(context, scheme.Name) as IAuthenticationRequestHandler;
if (handler != null && await handler.HandleRequestAsync())
{
return;
}
}
var defaultAuthenticate = await Schemes.GetDefaultAuthenticateSchemeAsync();
if (defaultAuthenticate != null)
{
var result = await context.AuthenticateAsync(defaultAuthenticate.Name);
if (result?.Principal != null)
{
context.User = result.Principal;
}
}
await _next(context);
}
}
And basically what happends is that the result has a Failure properties on it that contains my authentication exception, but since there is no check for that in the code, it will continue with the request to the next middleware in the pipeline. So I basically wrote my own AuthenticationMiddleware adding a check if the Failure has a value then return 403.
var defaultAuthenticate = await _schemas.GetDefaultAuthenticateSchemeAsync();
if (defaultAuthenticate != null)
{
var result = await context.AuthenticateAsync(defaultAuthenticate.Name);
if (result?.Principal != null)
context.User = result.Principal;
if (result?.Failure != null)
throw new AuthorizationException(result.Failure.Message);
}
await _next(context);
}
catch (AuthorizationException ex) when (!context.Response.HasStarted)
{
_logger.LogWarning(ex, "Unauthorized access encountered.");
context.Response.Clear();
context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
}
It's however not something that would except me to do, so if somebody knows why i need to do this I would be glad for the information.

Related

Unauthorized / token contains no permission when trying to use Microsoft Graph to send email

We have a registered application in Azure AD and set up client secrets to run it as a daemon app (with no user interaction). We have the API permissions Mail.Send and User.Read admin consented for Microsoft Graph API.
My understanding is to use construct a ConfidentialClientApplication to get an access token for the registered app, by which I can create a GraphServiceClient. Then I can use the client to send email as a user.
But I got the following exception saying there's no permission in the token: (but I did provide a scope for getting permission)
Message: The token contains no permissions, or permissions can not be understood.
Inner error:
AdditionalData:
request-id: omitted-xxxx-xxx-...31c53
date: 2020-03-13T23:41:08
ClientRequestId: omitted-xxxx-xxx-...31c57
at Microsoft.Graph.HttpProvider.SendAsync(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationToken cancellationToken)
at Microsoft.Graph.BaseRequest.SendRequestAsync(Object serializableObject, CancellationToken cancellationToken, HttpCompletionOption completionOption)
at Microsoft.Graph.BaseRequest.SendAsync(Object serializableObject, CancellationToken cancellationToken, HttpCompletionOption completionOption)
at GraphCallsFromServiceAccount.MyGraphClient.SendEmail(GraphServiceClient graphClient) in C:\Users\xxxx\source\repos\GraphCallsFromAccount\MyGraphClient.cs:line 109
Relevant Code:
// create a ConfidentialClientApplication:
var app = ConfidentialClientApplicationBuilder.Create(AppClientId)
.WithAuthority(new Uri("https://login.microsoftonline.com/"+ TenantId + "/oauth2/v2.0/token"))
.WithClientSecret(ClientSecretString)
.Build();
var scopes = new string[] { "https://graph.microsoft.com/.default" }; // if changed to "Mail.Send", it throws errors saying invalid scope.
GraphServiceClient graphClient = new GraphServiceClient(
new DelegateAuthenticationProvider(
async (requestMg) =>
{
// add access token to header
var result = await app.AcquireTokenForClient(scopes).ExecuteAsync();
requestMg.Headers.Authorization = new AuthenticationHeaderValue("bearer", result.AccessToken);
}));
// send email:
try
{
var toAddress = "john_doe#helloworld.com";
var SenderAddress = "jane_doe#helloworld.com";
var recipient = new Recipient()
{
EmailAddress = new EmailAddress()
{
Name = "John Doe",
Address = toAddress,
}
};
Message email = new Message
{
Body = new ItemBody
{
Content = "<b>hello world</b>",
ContentType = BodyType.Html,
},
Subject = "hello world",
ToRecipients = new List<Recipient>() { recipient },
};
Console.WriteLine("hello 2");
await graphClient.Users["john_doe#helloworld.com"].SendMail(email, false).Request().PostAsync();
}
catch (Exception ex)
{
Console.WriteLine("ex: " + ex);
}
How do I request the correct permissions put in the access token then? Thanks for help
Since you are acquiring a token as an app,
you are most likely not using application permissions.
You cannot use delegated permissions when running as an app,
as those only apply if running in the context of a user.
You'll need to add the Mail.Send application permission on Microsoft Graph API,
and an admin must then consent that.

Identity Server 3 + ASP.NET Core 2.0 MVC app - Federated single sign-out not including a redirect to ADFS before ending session

My web app is client to an Identity Server 3 STS, which is federated with ADFS for the external IdP. Sign-in works great. Sign-out from the STS is fine. But I have never been able to get IdSrv3 to redirect to ADFS for sign-out prior to ending the IdSrv3 session and ultimately redirecting to the app.
If I understand correctly, I should be able to have ADFS post back to the RP (IdSrv3) after signing out, at which point IdSrv3
Read the docs:
https://identityserver.github.io/Documentation/docsv2/advanced/federated-post-logout-redirect.html
As well as much of the anthology of the GitHub issues surrounding this topic of federated single sign-out.
Tracing through IdSrv3 I never see an attempt to redirect to ADFS for sign-out, so I assume I'm missing configuration here.
Once complexity is that I'm running IdSrv3 however my client apps are ASP.NET Core 2.0 so many of the samples don't cleanly reconcile with the latest Microsoft identity client middleware.
On the IdSrv3, these are (I believe) the relevant configuration components:
Configuration of Additional Identity Providers:
var wsFed = new WsFederationAuthenticationOptions
{
Wtrealm = ConfigurationManager.AppSettings["Wtrealm"],
MetadataAddress = metaDataAddress,
AuthenticationType = "ADFS",
Caption = "ACME ADFS",
SignInAsAuthenticationType = signInAsType
};
The IdSrv3 middleware:
coreApp.UseIdentityServer(
new IdentityServerOptions
{
SiteName = "eFactoryPro Identity Server",
SigningCertificate = Cert.Load(),
Factory = factory,
RequireSsl = true,
AuthenticationOptions = new AuthenticationOptions
{
IdentityProviders = ConfigureAdditionalIdentityProviders,
EnablePostSignOutAutoRedirect = true,
EnableSignOutPrompt = false,
EnableAutoCallbackForFederatedSignout = true
},
LoggingOptions = new LoggingOptions
{
EnableHttpLogging = true,
EnableKatanaLogging = true,
//EnableWebApiDiagnostics = true,
//WebApiDiagnosticsIsVerbose = true
}
});
coreApp.Map("/signoutcallback", cleanup =>
{
cleanup.Run(async ctx =>
{
var state = ctx.Request.Cookies["state"];
await ctx.Environment.RenderLoggedOutViewAsync(state);
});
});
});
Now for the Client side, an ASP.NET Core 2.0 MVC application:
Update: See accepted answer - the redirect to IdP for sign-out should have been handled on the IdSrv3 side with respect to redirecting to the external IdP (ADFS)
public static void ConfigureAuth(this IServiceCollection services,
ITicketStore distributedStore,
Options.AuthenticationOptions authOptions)
{
services.AddDataProtection();
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
options.DefaultSignOutScheme = CookieAuthenticationDefaults.AuthenticationScheme;
}).AddCookie(options =>
{
options.ExpireTimeSpan = TimeSpan.FromHours(8);
options.SlidingExpiration = true;
options.SessionStore = distributedStore;
})
.AddOpenIdConnect(options =>
{
options.Authority = authOptions.Authority;
options.ClientId = authOptions.ClientId;
options.ClientSecret = authOptions.ClientSecret;
options.ResponseType = "code id_token";
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("roles");
options.Scope.Add("email");
options.Scope.Add("offline_access");
options.RequireHttpsMetadata = false;
options.GetClaimsFromUserInfoEndpoint = true;
options.SaveTokens = true;
options.Events = new OpenIdConnectEvents()
{
OnRedirectToIdentityProviderForSignOut = n =>
{
var idTokenHint = n.ProtocolMessage.IdTokenHint;
if (!string.IsNullOrEmpty(idTokenHint))
{
var sessionId = n.HttpContext?.Session?.Id;
var signOutRedirectUrl = n.ProtocolMessage.BuildRedirectUrl();
if (sessionId != null)
{
n.HttpContext.Response.Cookies.Append("state", sessionId);
}
n.HttpContext?.Session?.Clear();
n.Response.Redirect(signOutRedirectUrl);
}
return Task.FromResult(0);
}
};
});
}
From the documentation I should be passing the "sign out message id" into that 'state' cookie. However, this extension method doesn't work in ASP.NET Core 2.0 as we don't really have access to OwinContext anymore.
var signOutMessageId = n.OwinContext.Environment.GetSignOutMessageId();
I've even tried instantiating a new OwinContext(n.HttpContext) to get at the environment dictionary - however, the value that the "GetSignOutMessageId()" obtains has a key of "id" which I can't find in the Owin variables.
It seems this cookie is really just necessary to persist state through all of the redirects so that after the PostLogoutUri of my client application is hit, which is currently set to "https://myapp/signout-callback-oidc", the message id can be used to finish cleaning up the session.
I'm also confused as to what role the "EnableAutoCallbackForFederatedSignout = true" setting plays on the IdSrv3 configuration.
From this description and looking at the code it would apear that this just saves me from having to set the "WReply" parameters on the ADFS signout:
https://github.com/IdentityServer/IdentityServer3/issues/2613
I would expect that ADFS would redirect to:
"https://myIdSrv3/core/signoutcallback" automatically if this settings was 'true'.
If anyone has any guidance to share it is much appreciated.
It turns out I was conflating some of the concepts in IdSrv3 that describe Federated Single Sign-Out initiated by the External Idp as opposed to my use case - sign-out initiated by the IdSrv3 client app, cascading "up" to the external IdP.
The root cause of this problem was in my UserService implementation. There I had overriden the "AuthenticateExternalAsync()" method, but did not specify the external identity provider in the AuthenticateResult object.
Here is the corrected implementation:
public override Task AuthenticateExternalAsync(ExternalAuthenticationContext context)
{
...
context.AuthenticateResult = new AuthenticateResult(
user.Id,
user.UserName,
new List<Claim>(),
context.ExternalIdentity.Provider);
return Task.FromResult(0);
}
Once the External Idp was specified in my AuthenticateResult, I was able to handle the WsFederationAuthenticationNotifications.RedirectToIdentityProvider event.
For the sake of completeness, here is my code to handle federated sign-out (client intiatited) from ADFS vis WsFed. It is more or less straight from the IdSrv3 documentation:
Notifications = new WsFederationAuthenticationNotifications()
{
RedirectToIdentityProvider = n =>
{
if (n.ProtocolMessage.IsSignOutMessage)
{
var signOutMessageId = n.OwinContext.Environment.GetSignOutMessageId();
if (signOutMessageId != null)
{
n.OwinContext.Response.Cookies.Append("state", signOutMessageId);
}
var cleanUpUri =
$#"{n.Request.Scheme}://{n.Request.Host}{n.Request.PathBase}/external-signout-cleanup";
n.ProtocolMessage.Wreply = cleanUpUri;
}
return Task.FromResult(0);
}
}
And finally, my /external-signout-cleanup implementation:
coreApp.Map("/external-signout-cleanup", cleanup =>
{
cleanup.Run(async ctx =>
{
var state = ctx.Request.Cookies["state"];
await ctx.Environment.RenderLoggedOutViewAsync(state);
});
});

Parse.com REST API Call gives invalid session token error

I have logged into parse from cloud code using Parse.User.logIn
After a successful login have retrieved session token using user._session
Parse.User.logIn(request.params.userdata.email, "sdfisadufhkasdjhf", {
success: function(user) {
response.success(user._sessionToken);
},
error: function(user, error) {
}
});
This session token is passed to the client which then makes a REST API call by setting the token in the header.
However, the rest API call is not successful and returns invalid session token error.
REST API call works perfect when I don't send session token for requests that don't need authentication
From chrome console, I can see that the headers are set correctly and the value of session token is same as Parse.User.current().getSessionToken()
in app.config()
$httpProvider.defaults.headers.post['X-Parse-Application-Id'] = "dxfhgfxhxhxhxxxxxxxxxxxxxxxxxxxxxdgerstrattgrft";
$httpProvider.defaults.headers.post['X-Parse-REST-API-Key'] = "gfhjjhfjfjjchfjcccccccccccccccccccccccccccccccccccc";
$httpProvider.defaults.headers.post['Content-Type'] = "application/json";
From controller
$scope.createGroup = function()
{
shan = $scope.creategroup;
$http.post('https://api.parse.com/1/functions/addGroup', $scope.creategroup,
{ headers: {
'X-Parse-Session-Token':sessionToken
}
}).success(function(data, status, headers, config) {
alert("success : "+JSON.stringify(data));
}).
error(function(data, status, headers, config) {
alert("error : "+JSON.stringify(data));
});
}

How to get Authenticated with spring security rest plugin in Grails

I'm using Grails version 2.4.3 . I am creating an application that supports RESTful APIs. Since access to these APIs should be authenticated , I tried out the Spring Security REST plugin. I checked out this example and what I could understand is , the /api/login controller is the authentication point which receives the user credentials in JSON format and after successful authentication it provides the acces token as response. I tried sending a POST request to /api/login/ with valid JSON data using the POSTMAN Rest Client. But it gives me the following error.
401 Unauthorized , Similar to 403 Forbidden, but specifically for use when authentication is possible but has failed or not yet been provided. The response must include a WWW-Authenticate header field containing a challenge applicable to the requested resource.
I also tried using IntellijIDEA's REST Client but doesn't work.
Then i tried by sending AJAX Request to /api/login/ with valid JSON data
, but getting 401 on console. What is the problem here? Is this the correct login end point? How can i get authenticated using JQuery?
Try this
$.ajax({
url: " http://localhost:8080/AppName/api/login",
type: "POST",
crossDomain: true,
data: JSON.stringify({"username":"yourusername" , "password":"yourpassword"}),
contentType: 'application/json; charset=utf-8',
dataType: "json",
success: function (response) {
console.log(response);
},
error: function (xhr, status) {
alert("error");
}
}) });
You can try this code for authentication,I am sending user id and password in request header you can try as you wish :-
inject following services:-
def springSecurityService
def authenticationManager
and use following code
def login = {
final String authorization = request.getHeader("Authorization");
if (authorization != null && authorization.startsWith("Basic")) {
boolean authResult = authenticateUser(authorization)
if (authResult) {
render response.status
} else {
render authFailed(response)
}
} else {
render authFailed(response)
}
}
protected boolean authenticateUser(String authorization) {
// Authorization: Basic base64credentials
def base64Credentials = authorization.substring("Basic".length()).trim();
byte[] credentials = base64Credentials.decodeBase64()
String actualCredential = new String(credentials)
// credentials format like username:password
final String[] values = actualCredential.split(":", 2);
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(values[0], values[1]);
try {
def authentication = authenticationManager.authenticate(authRequest);
def securityContext = SecurityContextHolder.getContext();
securityContext.setAuthentication(authentication);
def session = request.session;
session.setAttribute("SPRING_SECURITY_CONTEXT", securityContext);
}
catch (BadCredentialsException exception) {
return false
}
return true
}
protected HttpServletResponse authFailedResponse(HttpServletResponse response) {
response.setStatus(401)
response.setHeader("WWW-Authenticate", "Basic realm=\"nmrs_m7VKmomQ2YM3:\"")
return response;
}

ASP.NET MVC5 OWIN: Why User.Identity.IsAuthenticated == false after signing in via Facebook?

I am following the example of the VS2013 SPA Template - however am not using Bearer Tokens (this maybe the problem but would like to get this working just with cookies if possible).
Here is a cut down version of the relevant action method in my API Controller:
[HttpGet]
[AllowAnonymous]
public async Task<IHttpActionResult> ExternalLogin(string provider, string error = null)
{
if (!User.Identity.IsAuthenticated)
{
return new ChallengeResult(provider, this);
}
ExternalLoginData externalLogin = ExternalLoginData.FromIdentity(User.Identity as ClaimsIdentity);
// ...do stuff with externalLogin data
}
Here is the basic flow of requests:
Users clicks Facebook button, sends GET to /api/externallogin?provider=Facebook
User.Identity.IsAuthenticated returns false -> results in 401 being returned and middleware converts that into a 302 with the 'Location' header set to the facebook login page
Browser goes to the facebook login page
(https://www.facebook.com/dialog/oauth?response_type=code&client_id={myClientId}&redirect_uri=http%3A%2F%2Flocalhost%3A54819%2Fsignin-facebook&scope=user_birthday&state={someStateCode})
User signs in via Facebook -> results in browser making a call to the 'redirect_uri' but now with 'code' and 'state' parameters in the query string i.e. http://localhost:54819/signin-facebook?code={someCode}&state={someStateCode}
Response from the call to the 'redirect_uri' is a 302 with Location header set back to my application and also contains two 'Set-Cookie' headers:
Location: http://localhost:54819/api/en-gb/account/externallogin?provider=Facebook
Set-Cookie: .AspNet.Correlation.Facebook=; expires=Thu, 01-Jan-1970 00:00:00 GMT
Set-Cookie: .AspNet.ExternalCookie=Rv01CHd2onYtN_MHw2Bt71JSaOP71uRk7AP6kSilnAg7djXMh5fbZxlRCPuZhy8inhEF7ChNB261WVU3LGDuIaQmMXgz7tqXeNI-ji8qQFi2d64a720PbRVpWnkuHm2m8L87fkJAGQMJOku5gMrc0EZJfKNgjXiLv-c6Vo7PEzNch-CqcCPFHP0KBo7tGhDTbgJt-RvTzkkB1NL2JBc23eiaeda70oAW4P0NfIyj_i9mLUexHXz8Qooy9CBoLrN7Z198H_cawBfiMMF0tK1YFee2eH_TQxdmdKkUFVRz58EeIKyKUEEDswbQA9evPEHpD8BIlJPXi6R2scC44_INufXuKjHOt7LW3-sPRkUGbEWCWOn4d1B4FkHR_xOHtRpGpIdZU14xJLLiyFYKR0XxJiRlRIph8KKYnZHy61wMOl2yznOFqq3rzHOGhZ1xXEKmUlByiawPbNpdS9pNZVSHlGMbiz0FsOTf4_EVAKEXRQyxEbYjBBXD_5Ne6f7SpBqE; path=/; HttpOnly
Browser then sends GET request to the URL in the Location header from step 5 (back to my application), with the following cookie (as per the above 'Set-Cookie' directive):
Cookie: .AspNet.ExternalCookie=Rv01CHd2onYtN_MHw2Bt71JSaOP71uRk7AP6kSilnAg7djXMh5fbZxlRCPuZhy8inhEF7ChNB261WVU3LGDuIaQmMXgz7tqXeNI-ji8qQFi2d64a720PbRVpWnkuHm2m8L87fkJAGQMJOku5gMrc0EZJfKNgjXiLv-c6Vo7PEzNch-CqcCPFHP0KBo7tGhDTbgJt-RvTzkkB1NL2JBc23eiaeda70oAW4P0NfIyj_i9mLUexHXz8Qooy9CBoLrN7Z198H_cawBfiMMF0tK1YFee2eH_TQxdmdKkUFVRz58EeIKyKUEEDswbQA9evPEHpD8BIlJPXi6R2scC44_INufXuKjHOt7LW3-sPRkUGbEWCWOn4d1B4FkHR_xOHtRpGpIdZU14xJLLiyFYKR0XxJiRlRIph8KKYnZHy61wMOl2yznOFqq3rzHOGhZ1xXEKmUlByiawPbNpdS9pNZVSHlGMbiz0FsOTf4_EVAKEXRQyxEbYjBBXD_5Ne6f7SpBqE
THE PROBLEM: User.Identity.IsAuthenticated check returns False at this stage (in fact the User field is basically empty)
I would have thought, given that the AspNet.ExternalCookie is definitely being sent in the request at step 6 then the user is thereby Authenticated.
So, does anyone know what the middleware would be looking for at this stage in order for it to decode/decrypt/de-serialize the cookie and saturate the User???
Here is the Startup.Auth I have:
public void ConfigureAuth(IAppBuilder app)
{
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
//AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
AuthenticationType = Constants.AuthenticationTypes.MchAdminApplicationCookie,
SlidingExpiration = true,
ExpireTimeSpan = new TimeSpan(10, 0, 0)
});
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
//AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
AuthenticationType = Constants.AuthenticationTypes.MchApiApplicationCookie,
SlidingExpiration = true,
ExpireTimeSpan = new TimeSpan(10, 0, 0)
});
var facebook = new FacebookAuthenticationOptions
{
AppId = "mycode",
AppSecret = "mysecret",
AuthenticationType = "Facebook",
SignInAsAuthenticationType = DefaultAuthenticationTypes.ExternalCookie,
Provider = new FacebookAuthenticationProvider
{
OnAuthenticated = async ctx =>
{
if (ctx.User["birthday"] != null)
{
ctx.Identity.AddClaim(new Claim(ClaimTypes.DateOfBirth, ctx.User["birthday"].ToString()));
}
}
}
};
facebook.Scope.Add("user_birthday");
app.UseFacebookAuthentication(facebook);
}
I encountered the same problem. You should add two attributes to ExternalLogin action:
[OverrideAuthentication]
[HostAuthentication(DefaultAuthenticationTypes.ExternalCookie)]