I have started looking into Google signin and have added the normal provider as such.
ddGoogle(go =>
{
go.ClientId = "xxxxx";
go.ClientSecret = "-xxxxx";
go.SignInScheme = IdentityConstants.ExternalScheme;
});
My test method just to get it started looks like this
public ActionResult TestGoogle()
{
var redirectUrl = Url.Action(nameof(ExternalCallback), "Account", new { ReturnUrl = "" });
var properties = _signInManager.ConfigureExternalAuthenticationProperties("Google", redirectUrl);
return Challenge(properties, "Google");
}
All well and good I go to google Log in and get redirected with all required claims as expected.
The issue is when I call _signInManager.SignOutAsync() which does not seem to do anything. No errors, yet when I go back to my TestGoogle action I am redirected with all credentials to the callback.
Anything I am missing?
In your Logout Action add this code
return Redirect("https://www.google.com/accounts/Logout?continue=https://appengine.google.com/_ah/logout?continue=[[your_return_url_here]]");
This is how I configured my code:
Configure 2 Cookies, one (MainCookie) for local login and second (ExternalCookie) for google.
services.AddAuthentication("MainCookie").AddCookie("MainCookie", options =>
{
});
services.AddAuthentication("ExternalCookie").AddCookie("ExternalCookie", o =>
{
});
Configure google authentication as shown below:
services.AddAuthentication(
v =>
{
v.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
v.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
}).
AddGoogle("Google", googleOptions =>
{
googleOptions.ClientId = "xxx...";
googleOptions.ClientSecret = "zzz...";
googleOptions.SignInScheme = "ExternalCookie";
googleOptions.Events = new OAuthEvents
{
OnRedirectToAuthorizationEndpoint = context =>
{
context.Response.Redirect(context.RedirectUri + "&hd=" + System.Net.WebUtility.UrlEncode("gmail.com"));
return Task.CompletedTask;
}
};
});
TestGoogle() Method will redirect you to google login page.
You can then get the claims from google back like so:
public async Task<IActionResult> ExternalLoginCallback(string returnUrl = null, string remoteError = null)
{
var info = await HttpContext.AuthenticateAsync("ExternalCookie");
//Sign in to local cookie and logout of external cookie
await HttpContext.SignInAsync("MainCookie", info.Principal);
await HttpContext.SignOutAsync("ExternalCookie");
//ExternalCookie will be deleted at this point.
return RedirectToLocal(returnUrl);
}
If you want to now want to authenticate any method, you can do so as shown below:
[Authorize(AuthenticationSchemes = "MainCookie")]
public async Task<IActionResult> Contact()
{
//Only authenticated users are allowed.
}
Related
I have setup Asp.Net Identity in my application, and would like to have the following setup:
Have a selfmade username&password authentication as IdentityConstants.ApplicationScheme
Have external providers (e.g. Google, Facebook) which authenticate as IdentityConstants.ExternalScheme
My social login looks like this:
[HttpGet]
[Route("ExternalLogin")]
public IActionResult ExternalLogin([FromQuery] string provider, [FromQuery] bool rememberMe)
{
var redirectUrl = Url.Action("SocialLoginCallback", "SocialLogin", new
{
rememberMe
});
var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
return Challenge(properties, provider);
}
[HttpGet]
[Route("SocialLoginCallback")]
public async Task<IActionResult> SocialLoginCallback([FromQuery] bool rememberMe)
{
// Grab the external login information from the http context
var loginInfo = await _signInManager.GetExternalLoginInfoAsync();
if (loginInfo is null)
{
return Problem();
}
var signinResult = await _signInManager.ExternalLoginSignInAsync(loginInfo.LoginProvider, loginInfo.ProviderKey, rememberMe, true);
if (signinResult.Succeeded)
{
return Ok();
}
return Ok();
}
My startup google auth looks like:
authBuilder.AddGoogle(options =>
{
options.ClientId = Configuration["GoogleClientId"];
options.ClientSecret = Configuration["GoogleClientSecret"];
options.SignInScheme = IdentityConstants.ExternalScheme;
});
However, I noticed that the login is still executed with IdentityConstants.ApplicationScheme.
After examining why, the problem seems to stem from the var signinResult = await _signInManager.ExternalLoginSignInAsync(loginInfo.LoginProvider, loginInfo.ProviderKey, rememberMe, true); call. Internally this runs into a call to await Context.SignInAsync(IdentityConstants.ApplicationScheme, userPrincipal, authenticationProperties ?? new AuthenticationProperties());, which has the ApplicationScheme hardcoded.
How can I get this to work as I would like to have?
I would like to distinguish both logins in a middleware, and as of now, the only way to do this would be to check the respective claims, but I'd rather just to a simple check which auth scheme the login is using. Is this possible?
If you want to set the default authentication method ,You can set DefaultScheme in ConfigureServices/Startup.cs.
public void ConfigureServices(IServiceCollection services)
{
//......
services.AddAuthentication(option =>
{
option.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie(option =>
{
option.LoginPath = "/Identity/Account/Login";
})
.AddGoogle(option => {
option.ClientId = "xxx";
option.ClientSecret = "xxx";
});
}
I set the login page as the default Identity login page, when I access the action whitch has [Authorize] attribute , the login page has two ways to log in.
If the app have multiple instances of an authentication handler , You can use [Authorize(AuthenticationSchemes = xxxx)] to Select the scheme with the Authorize attribute.
You can read this document to learn more details about Authorize with a specific scheme in ASP.NET Core
I am new to ASP.NET Identity, I have this log in function on my ASP.NET Core 5 Web API:
[HttpPost("login")]
public async Task<IActionResult> login([FromBody] LogIn logIn)
{
var user = await _userManager.FindByEmailAsync(logIn.Email);
if (user == null)
{
return NotFound("User Not Found!");
}
else if (user != null && await _userManager.CheckPasswordAsync(user, logIn.Password))
{
var claimsIdentity = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Name, user.UserName),
//...
}, "Cookies");
var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
await Request.HttpContext.SignInAsync("Cookies", claimsPrincipal);
return Ok();
}
else
{
return BadRequest("Email and/or Password not valid!");
}
}
But when I sign in, I get this error:
System.InvalidOperationException: No sign-in authentication handler
is registered for the scheme 'Cookies'
How do I fix this? I will comment my setup.cs file because stack.
make sure you configure you services correctly, you need to add
In the Startup class, find the ConfigureServices method and type:
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{ // all your options
options.Cookie.HttpOnly = true;
// in dev
options.Cookie.SecurePolicy = _environment.IsDevelopment()
? CookieSecurePolicy.None : CookieSecurePolicy.Always;
options.Cookie.SameSite = SameSiteMode.Lax;
});
for e.g. in the new 5.0
.AddCookie(config =>
{
config.Cookie.HttpOnly = true;
//options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
config.Cookie.SameSite = SameSite.Lax;
config.Cookie.Name = CookieAuthenticationDefaults.AuthenticationScheme;
config.LoginPath = "/Login"; // your login path
});
Additional things
clear your cache
Enable HTTPS, find the option in VS
Turn off GDPR, which maybe now included in the template, and if the user does not accept it will not set the cookie.
I have an ASP.NET Core app that I am integrating with Auth0. After the authentication, I want to redirect to a page to collect information to create a local account, just like the default Facebook and Google extensions do.
I set up a main cookie, an external cookie and my Auth0 point. It then does a callback to the page (/Account/ExternalLogin), where I sign in to the main cookie after doing whatever they need to do, and redirect to a page that requires authorization (/Profile. This all works fine.
However, if I just try to go to that page rather than via the Login route, i get stuck in a loop.
I'm quite sure I'm missing just one stupid thing, but can't seem to get it.
I've tried to pretty much every combination of things I can figure out and have hit the wall. I'm sure it's something stupid.
Here's my relevant part of startup.cs
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
// Add authentication services
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = "MainCookie";
options.DefaultChallengeScheme = "Auth0";
})
.AddCookie("MainCookie", options =>
{
options.ForwardChallenge = "Auth0";
})
.AddCookie("External", options =>
{
})
.AddOpenIdConnect("Auth0", options =>
{
// Set the authority to your Auth0 domain
options.Authority = $"https://{Configuration["Auth0:Domain"]}";
// Configure the Auth0 Client ID and Client Secret
options.ClientId = Configuration["Auth0:ClientId"];
options.ClientSecret = Configuration["Auth0:ClientSecret"];
// Set response type to code
options.ResponseType = "code";
// Configure the scope
options.Scope.Clear();
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("email");
options.SignInScheme = "External";
// Set the callback path, so Auth0 will call back to http://localhost:3000/callback
// Also ensure that you have added the URL as an Allowed Callback URL in your Auth0 dashboard
options.CallbackPath = new PathString("/callback");
// Configure the Claims Issuer to be Auth0
options.ClaimsIssuer = "Auth0";
options.Events = new OpenIdConnectEvents
{
// handle the logout redirection
OnRedirectToIdentityProviderForSignOut = (context) =>
{
var logoutUri = $"https://{Configuration["Auth0:Domain"]}/v2/logout?client_id={Configuration["Auth0:ClientId"]}";
var postLogoutUri = context.Properties.RedirectUri;
if (!string.IsNullOrEmpty(postLogoutUri))
{
if (postLogoutUri.StartsWith("/"))
{
// transform to absolute
var request = context.Request;
postLogoutUri = $"{request.Scheme}://{request.Host}{request.PathBase}{postLogoutUri}";
}
logoutUri += $"&returnTo={ Uri.EscapeDataString(postLogoutUri) }";
}
context.Response.Redirect(logoutUri);
context.HandleResponse();
return Task.CompletedTask;
}
};
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
.AddRazorPagesOptions(options =>
{
options.Conventions.AuthorizePage("/Profile");
});
}
Here's AccountController
public class AccountController : Controller
{
public async Task Login(string returnUrl = "/")
{
var redirectUrl = Url.Page("/ExternalLogin", pageHandler: "Callback", values: new { returnUrl });
await HttpContext.ChallengeAsync("Auth0", new AuthenticationProperties() { RedirectUri = redirectUrl });
}
[Authorize]
public async Task Logout()
{
await HttpContext.SignOutAsync("External");
await HttpContext.SignOutAsync("MainCookie");
await HttpContext.SignOutAsync("Auth0", new AuthenticationProperties
{
RedirectUri = Url.Action("Index", "Home")
});
}
}
SO we redirect to ExternalLogin callback. Currently there is just a submit button that goes to the Confirm callback which completes the login. This will eventually be replaced with a check to see if I have an account for them, and force them to register.
public class ExternalLoginModel : PageModel
{
public IActionResult OnPost(string provider, string returnUrl = null)
{
var redirectUrl = Url.Page("./ExternalLogin", pageHandler: "Callback", values: new { returnUrl });
return new ChallengeResult(provider, null);
}
public async Task<IActionResult> OnGetCallbackAsync(string returnUrl = null, string remoteError = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
if (remoteError != null)
{
ErrorMessage = $"Error from external provider: {remoteError}";
return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
}
return Page();
}
public async Task<IActionResult> OnPostConfirmAsync()
{
var claimsPrincipal = await HttpContext.AuthenticateAsync("External");
await HttpContext.SignInAsync("MainCookie", claimsPrincipal.Principal);
await HttpContext.SignOutAsync("External");
return RedirectToPage("/Profile");
}
}
So when I go /Account/Login, it correctly sends me to Auth0, then to ExternalLogin, and I can click the button and set the Main Cookie. This then lets me access /Profile.
However, If I'm not already authorized, If I execute /Profile, I then kick over to Auth0, but after authenticating I just get stuck in a loop like this.
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request starting HTTP/2.0 GET https://localhost:44375/profile
Microsoft.AspNetCore.Routing.EndpointMiddleware:Information: Executing endpoint 'Page: /Profile'
Microsoft.AspNetCore.Mvc.RazorPages.Internal.PageActionInvoker:Information: Route matched with {page = "/Profile", action = "", controller = ""}. Executing page /Profile
Microsoft.AspNetCore.Authorization.DefaultAuthorizationService:Information: Authorization failed.
Microsoft.AspNetCore.Mvc.RazorPages.Internal.PageActionInvoker:Information: Authorization failed for the request at filter 'Microsoft.AspNetCore.Mvc.Authorization.AuthorizeFilter'.
Microsoft.AspNetCore.Mvc.ChallengeResult:Information: Executing ChallengeResult with authentication schemes ().
Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler:Information: AuthenticationScheme: Auth0 was challenged.
Microsoft.AspNetCore.Mvc.RazorPages.Internal.PageActionInvoker:Information: Executed page /Profile in 11.2594ms
Microsoft.AspNetCore.Routing.EndpointMiddleware:Information: Executed endpoint 'Page: /Profile'
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request finished in 28.548ms 302
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request starting HTTP/2.0 POST https://localhost:44375/callback application/x-www-form-urlencoded 375
Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler:Information: AuthenticationScheme: External signed in.
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request finished in 113.1223ms 302
Changing options.DefaultChallengeScheme = "Auth0" to options.DefaultChallengeScheme = "MainCookie" was all that was needed.
I have an ASP.NET Core 2.1 MVC application and I'm trying to use Azure AD to authenticate. The application redirects to the Microsoft login page but when I logout and then go back to the homepage of the application it automatically logs back in.
I've tried calling https://login.microsoftonline.com/common/oauth2/v2.0/logout and clearing the cookies but to no avail.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
.AddAzureAD(options => Configuration.Bind("AzureAd", options));
services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options =>
{
options.Authority = options.Authority + "/v2.0/";
options.TokenValidationParameters.ValidateIssuer = false;
options.Events.OnRedirectToIdentityProviderForSignOut = async context =>
{
var h = new HttpClient();
var r = await h.GetAsync($"https://login.microsoftonline.com/common/oauth2/v2.0/logout?post_logout_redirect_uri=https%3A%2F%2Flocalhost%2%3A5001%2F");
foreach (var cookie in context.Request.Cookies.Keys)
{
context.Response.Cookies.Delete(cookie);
}
};
options.Events.OnTokenResponseReceived = async conext =>
{
var t = 1;
};
});
services.AddMvc(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
public async Task<IActionResult> Logout()
{
var result = SignOut("AzureAD", "AzureADOpenID", "AzureADCookie");
return result;
}
Please check my way to add Azure AD authentication to ASP.NET Core 2.1 MVC application. The tool will add the authentication code for you. What you need to do is binding your sign in/out button to the method.
1.Click Connected Services->choose Authentication with Azure Active Directory.
2.You need to provide a login button for trigger the login page.
3.Input your tenant name for Domain and choose a way for providing application settings.
4.Click finish button to complete the configuration.
5.Delete app.UseBrowserLink() in Startup.cs.
6.Call SignOut() method in AccountController.cs to sign out the user. It works well.
[HttpGet]
public IActionResult SignOut()
{
var callbackUrl = Url.Action(nameof(SignedOut), "Account", values: null, protocol: Request.Scheme);
return SignOut(
new AuthenticationProperties { RedirectUri = callbackUrl },
CookieAuthenticationDefaults.AuthenticationScheme,
OpenIdConnectDefaults.AuthenticationScheme);
}
Since you are using the Microsoft.AspNetCore.Authentication.AzureAD.UI library , you can directly redirect user to https://localhost:xxxxx/AzureAD/Account/SignOut for sign out , Source code :
[HttpGet("{scheme?}")]
public IActionResult SignOut([FromRoute] string scheme)
{
scheme = scheme ?? AzureADDefaults.AuthenticationScheme;
var options = Options.Get(scheme);
var callbackUrl = Url.Page("/Account/SignedOut", pageHandler: null, values: null, protocol: Request.Scheme);
return SignOut(
new AuthenticationProperties { RedirectUri = callbackUrl },
options.CookieSchemeName,
options.OpenIdConnectSchemeName);
}
You can now remove the OnRedirectToIdentityProviderForSignOut event .
In .NET Core, signing out of Active Directory is now built into the provided Account controller > SignOut action. You can provide a button like this:
<a asp-area="AzureAD" asp-controller="Account" asp-action="SignOut">Sign out</a>
This generates the url: /AzureAD/Account/SignOut
https://github.com/Azure-Samples/active-directory-aspnetcore-webapp-openidconnect-v2/tree/master/1-WebApp-OIDC/1-6-SignOut
We ran into a problem using the .Net Core 2.1 Identity Server with OpenIdConnect. Our environment is a multi resource landscape and one centralized authorization server. All resources including the authorization server are accessible over a local nginx with default configuration.
For examle:
http://localhost/authserver
http://localhost/resource1
http://localhost/resource2
The problem occurred, while an unauthorized resource owner is redirected to the login page of the authorization provider, using the authorization code flow. When login with credentials and they are valid, the authorization server calls
http://localhost/connect/authorize/callback? and should normally http://localhost/resource1/connect/authorize?
We are using the default configuration of the .net Core Identity Server.
We found a workarount using a subdomain for localhost, so the path is correct, but ran into another problem, executing the final resource callback.
The resource callback for the client is defined as http://localhost/resource1/callback, but this callback path is not set for the resource, so the callback turns into a 404 status.
authenticationBuilder.AddOpenIdConnect(o =>
{
o.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
o.RequireHttpsMetadata = false;
o.ClientId = myClient;
o.ClientSecret = mySecret;
o.CallbackPath = new PathString(/callback or absolute url); ;
o.Authority = http://localhost/authserver;
o.ResponseType = OpenIdConnectResponseType.Code;
o.SaveTokens = true;
o.GetClaimsFromUserInfoEndpoint = true;
o.Events = new OpenIdConnectEvents()
{
OnAuthenticationFailed = c =>
{
c.HandleResponse();
c.Response.StatusCode = 500;
c.Response.ContentType = "text/plain";
return c.Response.WriteAsync("An error occurred processing your authentication.");
}
};
o.Scope.Remove("profile");
});
The login in account controller of the authorization server is defined as follow:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginInputModel model)
{
var authorizationContext = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl);
if (ModelState.IsValid)
{
var user = await _userManagementApi.LoginAsync(
new Credentials() {
Email = model.Username,
Password = model.Password
}.ToViewModel()
);
if (user != null)
{
var claims = new List<Claim>() {
new Claim("sub", user.Id.ToString()),
};
AuthenticationProperties props = new AuthenticationProperties
{
IsPersistent = false,
};
await HttpContext.SignInAsync(user.Id, user.Email, props, claims.ToArray());
if (_interaction.IsValidReturnUrl(model.ReturnUrl))
{
return Redirect(model.ReturnUrl);
}
return Redirect("/");
}
}
return View(new LoginViewModel
{
Username = model.Username,
Password = model.Password,
ReturnUrl = model.ReturnUrl,
RememberLogin = model.RememberLogin,
ExternalProviders = new List<ExternalProvider>()
});
}
This should be the default login.
Are there any configurations to solve the problem on the resource side and the authorization server side, or do we need to work with the workaround using subdomains for all our resources behind the nginx.
The whole setup works without problems, if we don't use the nginx and just work with the resources directly.