The current follow of my application is that I'm getting access-token from the front-end then using that token my backend is calling the External-Login provider to conform the user identity and retrieve the extra information-from the login provider.
As in this method I have to setup and configure login provider for each client(web ,android ). I'm tryin to use Microsoft.AspNetCore.Authentication.Facebook in my back-end to handle the facebook login using the claims which I'm getting from facebook I want to use that to generate Openidic JWT token. Everything works fine but statement
return SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme);
is throwing an Exception
An authorization or token response cannot be returned from this endpoint.
here is the question with same error but context is different ASP.NET Core Openiddict throws "An OpenID Connect response cannot be returned from this endpoint"
here is another question with similar approach but different How to Generate AccessToken for user who is logged in with External Providers
Here is my startup.cs code
public void ConfigureServices(IServiceCollection services)
{
var connectionString = Configuration["ConnectionStrings:ApplicationDbContext"];
services.AddEntityFrameworkNpgsql();
services.AddDbContext<ApplicationDbContext>(
opts =>
{
opts.UseNpgsql(connectionString, b => b.MigrationsAssembly("Data.Repository"));
opts.UseOpenIddict();
}
);
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddAuthentication().AddFacebook(facebookOptions =>
{
facebookOptions.AppId = "APP_ID";
facebookOptions.AppSecret = "APP_SECRET";
facebookOptions.SaveTokens = true;
});
var validIssuer = Configuration["Token:Issuer"];
services.AddAuthentication()
.AddJwtBearer(cfg =>
{
cfg.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = validIssuer,
IssuerSigningKey = securityKey,
ValidateIssuer = !String.IsNullOrEmpty(validIssuer),
ValidateAudience = false,
ValidateLifetime = true,
ValidateActor = false,
ValidateIssuerSigningKey = true
};
});
services.AddOpenIddict(options =>
{
// Register the Entity Framework stores.
options.AddEntityFrameworkCoreStores<ApplicationDbContext>();
options.AddMvcBinders();
options.EnableTokenEndpoint("/api/account/token");
options.UseJsonWebTokens();
options.AllowPasswordFlow();
options.AllowCustomFlow("urn:ietf:params:oauth:grant-type:facebook_access_token");
options.AllowCustomFlow("urn:ietf:params:oauth:grant-type:google_access_token");
options.AllowCustomFlow("urn:ietf:params:oauth:grant-type:microsoft_access_token");
options.DisableHttpsRequirement();
options.AddSigningKey(securityKey);
});
services.Configure<IdentityOptions>(options =>
{
//OpenId
options.ClaimsIdentity.UserNameClaimType = OpenIdConnectConstants.Claims.Name;
options.ClaimsIdentity.UserIdClaimType = OpenIdConnectConstants.Claims.Subject;
options.ClaimsIdentity.RoleClaimType = OpenIdConnectConstants.Claims.Role;
// Password settings
options.Password.RequireDigit = true;
options.Password.RequiredLength = 8;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = true;
options.Password.RequireLowercase = false;
// Lockout settings
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(30);
options.Lockout.MaxFailedAccessAttempts = 10;
// User settings
options.User.RequireUniqueEmail = true;
});
services.AddCors(o => o.AddPolicy("CorsPolicy", builder =>
{
builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
}));
// Add framework services.
services.AddMvc(options =>
{
options.Filters.Add(new GlobalExceptionFilter());
});
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public virtual void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, RoleManager<IdentityRole> roleManager)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
app.UseAuthentication();
app.UseCors("CorsPolicy");
app.UseMvc();
}
This is the controller which is handling External-Login
[HttpGet]
[AllowAnonymous]
public IActionResult ExternalLogin(string provider = "Facebook", string returnUrl = null)
{
// Request a redirect to the external login provider.
var redirectUrl = Url.Action(nameof(ExternalLoginCallback), "Auth", new { returnUrl });
var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
return Challenge(properties, provider);
}
[HttpGet]
[AllowAnonymous]
public async Task<IActionResult> ExternalLoginCallback(string returnUrl = null, string remoteError = null)
{
if (remoteError != null)
{
return BadRequest("Error from external provider");
}
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
{
return BadRequest();
}
try
{
var ticket = await _accountService.TokenExchangeAsync(info);
return SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme);
}
catch (Exception ex)
{
return BadRequest(new OpenIdConnectResponse
{
Error = OpenIdConnectConstants.Errors.ServerError,
ErrorDescription = ex.Message
});
}
}
Here the code that handles creating user and other related stuff and it is called by ExternalLoginCallback
public async Task<AuthenticationTicket> TokenExchangeAsync (ExternalLoginInfo info)
{
var claims = info.Principal.Claims;
var profile = new Profile
{
email = claims.FirstOrDefault(x => x.Type == ClaimTypes.Email).Value,
id = claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier).Value,
name = claims.FirstOrDefault(x => x.Type == ClaimTypes.Name).Value
};
// Find the user
var user = await _userManager.FindByLoginAsync(info.LoginProvider, profile.id);
if (user == null)
{
if (string.IsNullOrEmpty(profile.email))
throw new Exception("Email is not specified in the user profile for the provider.");
await RegisterExtenalUserAsync(profile, info.LoginProvider);
/// Try to find the user
user = await _userManager.FindByLoginAsync(info.LoginProvider, profile.id);
if (user == null)
{
// Return bad request if the user doesn't exist
throw new Exception("Invalid profile or provider.");
}
}
// Create the principal
var principal = await _signInManager.CreateUserPrincipalAsync(user);
// Claims will not be associated with specific destinations by default, so we must indicate whether they should
// be included or not in access and identity tokens.
foreach (var claim in principal.Claims)
{
// For this sample, just include all claims in all token types.
// In reality, claims' destinations would probably differ by token type and depending on the scopes requested.
claim.SetDestinations(OpenIdConnectConstants.Destinations.AccessToken, OpenIdConnectConstants.Destinations.IdentityToken);
}
// Create a new authentication ticket for the user's principal
var ticket = new AuthenticationTicket(principal, new AuthenticationProperties(),
OpenIdConnectServerDefaults.AuthenticationScheme);
// Include resources and scopes, as appropriate
var scope = new[]
{
OpenIdConnectConstants.Scopes.OpenId,
OpenIdConnectConstants.Scopes.Email,
OpenIdConnectConstants.Scopes.Profile,
OpenIddictConstants.Scopes.Roles
};
ticket.SetScopes(scope);
// Sign in the user
return ticket;
}
Here is the Log file
Project> info: Microsoft.AspNetCore.Mvc.SignInResult[1]
Project> Executing SignInResult with authentication scheme (ASOS) and the following principal: System.Security.Claims.ClaimsPrincipal.
Project> info: Microsoft.AspNetCore.Mvc.SignInResult[1]
Project> Executing SignInResult with authentication scheme (ASOS) and the following principal: System.Security.Claims.ClaimsPrincipal.
Project> info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2]
Project> Executed action Project.Controllers.AuthController.ExternalLoginCallback (Project) in 9068.4071ms
Project> info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2]
Project> Executed action Project.Controllers.AuthController.ExternalLoginCallback (Project) in 9068.4071ms
Project> fail: Microsoft.AspNetCore.Server.Kestrel[13]
Project> Connection id "0HL86DQMN0Q30", Request id "0HL86DQMN0Q30:00000003": An unhandled exception was thrown by the application.
Project> System.InvalidOperationException: An authorization or token response cannot be returned from this endpoint.
Project> at AspNet.Security.OpenIdConnect.Server.OpenIdConnectServerHandler.<SignInAsync>d__6.MoveNext()
Project> --- End of stack trace from previous location where exception was thrown ---
Any help will be appreciated!