IdentityServer4 - Login directly from an external provider - identityserver4

I've implemented the option to login from Azure AD. And the client type I'm using is Hybrid. So now, when a user enters a restricted control on my application, he is being redirected to a login page (on the IdentityServer application site) where he can either enter a username and password or login with an Azure AD account.
What I want to be able to do is skip the login page and redirect the user directly to the MS AD login page. Meaning, the user will click a "Login" link on the website, and that will lead him to the Azure AD login page. Once he successful logged in, he will be redirected back to my application (basically the same flow, just save that extra step of entering IdentityServer login page and clicking the external login button).
Is this possible?

In the client options, try setting EnableLocalLogin to false. From the docs:
EnableLocalLogin
Specifies if this client can use local accounts, or external IdPs only. Defaults to true.
I'm using Asp.Net Core Identity as well, and I set the AccountsController to bypass the local page if EnableLocalLogin is false and there is only one external provider, or if the idP is explicitly set in the request.
[HttpGet]
[AllowAnonymous]
public async Task<IActionResult> Login(string returnUrl = null)
{
// Clear the existing external cookie to ensure a clean login process
await HttpContext.Authentication.SignOutAsync(_externalCookieScheme);
var context = await _interaction.GetAuthorizationContextAsync(returnUrl);
if (context?.IdP != null)
{
// if IdP is passed, then bypass showing the login screen
return ExternalLogin(context.IdP, returnUrl);
}
var vm = await BuildLoginViewModelAsync(returnUrl, context);
if (vm.EnableLocalLogin == false && vm.ExternalProviders.Count() == 1)
{
// only one option for logging in
return ExternalLogin(vm.ExternalProviders.First().AuthenticationScheme, returnUrl);
}
return View(vm);
}

Related

How to redirect on backend for Angular application using Itfoxtec to access app through Azure Active Directory

I am new to using ITfoxtec for Azure Active Directory SAML logins. I read the StackOverflow entry for Nuget ITfoxtec SAML & Angular (and other similar entries for CORS issues), but I still do not understand how to adapt the GitHub Angular example from https://github.com/ITfoxtec/ITfoxtec.Identity.Saml2 to my needs. When running the ITfoxtec GitHub example, the Login method of the AuthController.cs file is immediately executed when I launch the test Angular application, and brings up the Azure Active Directory login prompt.
For my application, I need to click a "Login using Azure Active Directory" button on the Angular front end to call a backend method that can then redirect to another method to attempt login.
.NetCore C# code:
SSOController.cs file:
// This method is called by an Angular front end button when the user wishes to log in via Azure Active Directory SSO
[AllowAnonymous]
[Route("AzureAuth")]
[HttpGet]
public IActionResult AzureAuth(string returnUrl = null)
{
var binding = new Saml2RedirectBinding();
Saml2Configuration config = GetSamlConfig();
binding.SetRelayStateQuery(new Dictionary<string, string> { { relayStateReturnUrl, returnUrl ?? Url.Content("https://localhost:44397/api/sso/AssertionConsumerService") } });
//return binding.Bind(new Saml2AuthnRequest(config)).ToActionResult();
// This gives a CORS error, so we have do ensure that we do the redirection at the backend
// so we try redirecting with "RedirectToAction"
return RedirectToAction("https://localhost:44397/api/sso/AssertionConsumerService");
}
My AssertionConsumerService() method (located in Dev at "https://localhost:44397/api/sso/AssertionConsumerService"), which I need to be redirected to:
[Route("AssertionConsumerService")]
[HttpPost]
public async Task<IActionResult> AssertionConsumerService(HttpRequestMessage request)
{
// After user enters AAD SSO information, redirect should point to here.
// This API endpoint is hit if I test from Azure Enterprise Application SSO testing with the redirect API set to this method.
// I do not understand how to do backend redirects from AzureAuth() method to this method, and ensure that the HTTP request data is correct.
}
Just a follow up to my own question. For logging in directly from the Angular front end, I am having success with using "#azure/msal-angular". Once the end user clicks the "Log in with Azure Active Directory" button and is authenticated back to the frontend, I forward the authentication details to the backend for authorization checks.
I am still using ITfoxtec at the backend to process what can be directly sent from the "Azure Enterprise Applications > Set up single sign on > Test single sign-on with ..." for testing purposes. With the Azure "App registrations > Authentication > Platform Configuration" set to "Single-Page Application", I am making good progress in development and testing.
Sounds like you got a solution.
You can load the Angular application before login if it is hosted a place in the ASP.NET application that do not require the user to be authenticated. Then you can start the login process your selv and validate if the user is authenticated.

MSAL React is not signing out from application instead it is also signing out from other tabs which are authenticated sessions

I am trying to logout the user in my react application which uses MSAL and with user account abc#mod123.onmicrosoft.com.
Now, there is a tab already opened with portal.azure.com for the same user. When the user gets logged out from the React application, while we are changing the tenant or accessing some links in portal.azure.com, it is asking to reenter the credentials for the user that is signed in before.
It means the Logout is happening even for other url which is having authenticated session for the same user , which is in other tab and opened.
Thus, the user is signing out from identity server instead of application. can any one help on this...
Here is the code snippet for logout,
const { instance } = useMsal();
const isAuthenticated = useIsAuthenticated();
if (isAuthenticated)
instance.logoutRedirect();
MSAL.js logoutRedirect method in v2 - that clears the cache in browser storage and redirects the window to the Azure Active Directory (Azure AD) sign-out page. After sign-out, Azure AD redirects back to the page that invoked logout by default.
Reference : https://learn.microsoft.com/en-us/azure/active-directory/develop/scenario-spa-sign-in?tabs=javascript2#sign-out-with-a-popup-window

IdentityServer4 - What is the difference between 'RedirectUri' and 'ReturnUrl'

I'm new to both Identity Server and OAuth 2.0 and am struggling to understand what is happening with each network request after I login.
I have set up a very basic authorization code grant login process with identity server that results in the following series of network requests...
The user attempts to access a resource with the [Authorize] attribute on the client (/home/secret)
They are redirected to the login screen
They login
They can now access the protected resource
I'm struggling to understand what is happening at the point the callback url is triggered.
I understand that redirect_uri is an OAuth term that refers to an address on the client the authorization server (identity server in this case) will send the authorization code to (setup against a client with the setting RedirectUris). This accounts for the signin-oidc request above...
...but what about callback? This url appears to be stored as a ReturnUrl parameter after the user is challenged and redirected to the login page.
What is the difference between this ReturnUrl and the standard OAuth redirect_uri?
In OAuth tutorials I've looked at, they describe the key exchange process as follows...
Authorization server checks username and password
Sends authorization_code to redirect_uri
authorization_code, client_id and client_secret sent back from client to authorization server
authorization_code is checked. access_token sent to redirect_uri
access_token is used to access protected resource
I'm struggling to map this process to what Identity Server appears to be doing.
Any help would be much appreciated!
To understand the need for this parameter we must take into account that in the authorization endpoint we can obtain different types of interactions with the user and that they can also be chained one after the other before being redirected to the client application. For example, an interaction could be the presentation of a Login view and, after a successful sign-in, a Consent screen.
User interactions
The conditions of the request will be evaluated to determine the type of interaction that must be presented to the user. These are some of the possible interactions when you access to the /authorize endpoint:
The user must be redirected to the Login view.
The user must be redirected to the Consent view.
The user must be redirected to the Access Denied view.
The request prompt parameter is none but the user is not authenticated or is not active. A login_required error will be returned.
The request prompt parameter is none, consent is requires but there isn't consent yet. A consent_required error will be returned.
...
And these are some of the conditions to determine which interaction should be used:
Prompt mode (login, none, consent)
If the user is or not authenticated
If user is or not active
If client allows or not local logins
...
Where does the url parameter come from
When you configure IdentityServer4 you can configure the name of this parameter by setting the option UserInteraction.LoginReturnUrlParameter. It is also possible to configure the path of the login endpoint:
services.AddIdentityServer(options =>
{
options.UserInteraction.LoginReturnUrlParameter = "myParamName"; // default value = "returnUrl"
options.UserInteraction.LoginUrl = "/user/login"; // default value = "/account/login"
})
And the value of this parameter is the constant value AuthorizeCallback composed of the Authorize constant and "/callback":
public const string Authorize = "connect/authorize";
public const string AuthorizeCallback = Authorize + "/callback";
Where is this parameter used?
In your case your /authorize endpoint has resolved to redirect to the login view. Notice the returnUrl paremeter added to the login url:
/login?ReturnUrl=/connect/authorize/callback
We can see that this parameter is used in the model of the login view. The following code is extracted from the UI proposed by IdentityServer4 Quickstart.UI:
Login (get). Shows the login page
[HttpGet]
public async Task<IActionResult> Login(string returnUrl)
{
// build a model so we know what to show on the login page
var vm = await BuildLoginViewModelAsync(returnUrl); // returnUrl is added to the LoginViewModel.ReturnUrl property
...
return View(vm);
}
Login (post). Validate credentials and redirect to returnUrl:
[HttpPost]
public async Task<IActionResult> Login(LoginInputModel model, string button)
{
...
if (_users.ValidateCredentials(model.Username, model.Password))
{
...
await HttpContext.SignInAsync(isuser, props); // Creates the "idsrv" cookie
...
return Redirect(model.ReturnUrl); // If we come from an interaction we will end up being redirected to ReturUrl
...
}
}
But I guess your question is why doesn't the idp just redirect to the client application using the redirectUri (signin-oidc) instead of using a local returnUrl?
IdentityServer will receive the request again to re-evaluate if other interaction is necessary before redirect to the client application, for example a consent interaction. Successive requests will be authenticated since we already have a session cookie.
These callbacks to /authorize/callback will take into account additional conditions to the previous ones to determine the next interaction, for example:
If there was already consent or not
If user accepted consent
If user denied consent
...
And what about the original url? (your /home/secret)
This time we are in your client app...
The authentication middleware keeps the original request data in a HttpContext.Feature, this way it will be available later in the handler.
context.Features.Set<IAuthenticationFeature>(new AuthenticationFeature
{
OriginalPath = context.Request.Path,
OriginalPathBase = context.Request.PathBase
});
The base class AuthenticationHandler retrieves this feature in two variables:
protected PathString OriginalPath => Context.Features.Get<IAuthenticationFeature>()?.OriginalPath ?? Request.Path;
protected PathString OriginalPathBase => Context.Features.Get<IAuthenticationFeature>()?.OriginalPathBase ?? Request.PathBase;
When the user attempts to access a resource with the [Authorize] attribute on the client (/home/secret) What it does is invoke the challenge action for your remote handler.
This is the code for the challenge action for OpenIdConnectHandler. This code of the challenge is very similar in other handlers: OauthHandler, CookieAuthenticationHandler.
if (string.IsNullOrEmpty(properties.RedirectUri))
{
properties.RedirectUri = OriginalPathBase + OriginalPath + Request.QueryString;
}
So, the original path "/home/secret" is stored in the AuthenticationProperty.RedirectUri. This AuthenticationProperty is encoded in the state parameter to be sent to the /authorize endpoint.
OAuth2 establishes that if the identity provider receives this parameter, it must return the same value in the response:
state
REQUIRED if the "state" parameter was present in the client
authorization request. The exact value received from the
client.
The challenge action redirects to the idp /authorize endpoint with the OAuth2 parameters including state.
When the idp interaction finish it will redirect us back to our client application with the autentication response, which includes the state value.
The remote handler captures this callback (/signin-oidc), decodes the AuthenticationProperties and invokes the SignIn action of the SignIn scheme.
The SignIn scheme is configured in the handler options SignInScheme property or, if not configured, it will use the default scheme for this action (usually "Cookies").
The SignIn invokation occurs in the abstract base class RemoteAuthenticationHandler, base class of all remote handlers. Notice the parameter ticketContext.Properties. This properties include our original url in the RedirectUri property:
await Context.SignInAsync(SignInScheme, ticketContext.Principal, ticketContext.Properties);
The Cookie handler SignIn action creates the session cookie for our client application (it should not be confused with the SSO cookie created in the idp "idsrv") and uses the RedirectUri property to redirect to the original path: /home/secret

Identity Server 4 - federated logout of google when used as an idp

I have google configured as an external identity provider. How do I configure IdentityServer to also log out of this external identity provder as well as all my client applications?
FYI, the client application sign out is already working. Just want to log user out of google as well.
Anwer
Google doesnt supprot it Signout for Google External Identity Provider isn't working
Background Information:
When a user is signing-out of IdentityServer, and they have used an external identity provider to sign-in then it is likely that they should be redirected to also sign-out of the external provider. Not all external providers support sign-out, as it depends on the protocol and features they support.
To detect that a user must be redirected to an external identity provider for sign-out is typically done by using a idp claim issued into the cookie at IdentityServer. The value set into this claim is the AuthenticationScheme of the corresponding authentication middleware. At sign-out time this claim is consulted to know if an external sign-out is required.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Logout(LogoutInputModel model)
{
// build a model so the logged out page knows what to display
var vm = await _account.BuildLoggedOutViewModelAsync(model.LogoutId);
var user = HttpContext.User;
if (user?.Identity.IsAuthenticated == true)
{
// delete local authentication cookie
await HttpContext.SignOutAsync();
// raise the logout event
await _events.RaiseAsync(new UserLogoutSuccessEvent(user.GetSubjectId(), user.GetName()));
}
// check if we need to trigger sign-out at an upstream identity provider
if (vm.TriggerExternalSignout)
{
// build a return URL so the upstream provider will redirect back
// to us after the user has logged out. this allows us to then
// complete our single sign-out processing.
string url = Url.Action("Logout", new { logoutId = vm.LogoutId });
// this triggers a redirect to the external provider for sign-out
return SignOut(new AuthenticationProperties { RedirectUri = url }, vm.ExternalAuthenticationScheme);
}
return View("LoggedOut", vm);
}
Ripped directly from the documentation Sign-out of External Identity Providers
This is what I did. On the logged out view, I added an iframe and a button, that when clicked loads the google logout url in the iframe. This seems to be working well.

signin from external (but trusted) clients in identity server 4

I had a requirement that the user could enter his password and username directly on the client to sign on.
Without much trouble i just created a very simple extra authenticate action within the same application as identity server that looks like the following.
public async Task<IActionResult> AuthenticateUser(
[FromBody] LoginInputModel model,
[FromServices] AzureB2CUserService userService
)
{
var context = new ResourceOwnerPasswordValidationContext { Password = model.Password, UserName = model.Username };
await userService.ValidateAsync(context);
if (context.Result.Subject != null)
{
AuthenticationProperties props = null;
// only set explicit expiration here if persistent.
// otherwise we reply upon expiration configured in cookie middleware.
if (AccountOptions.AllowRememberLogin && model.RememberLogin)
{
props = new AuthenticationProperties
{
IsPersistent = true,
ExpiresUtc = DateTimeOffset.UtcNow.Add(AccountOptions.RememberMeLoginDuration)
};
};
// issue authentication cookie with subject ID and username
// var user = _users.FindByUsername(model.Username);
await HttpContext.SignInAsync(context.Result.Subject.GetSubjectId(), model.Username, props, context.Result.Subject.Claims.ToArray());
return Ok(context.Result);
}
ModelState.AddModelError("", AccountOptions.InvalidCredentialsErrorMessage);
return BadRequest(ModelState);
}
from the single page application I then call this action and if success, i know that a local authentication cookie has been for identity server.
Then i do a .userManager.signinSilent() from the oidc-client and since the cookie is there, it will get a token exactly the same way if I had used an implicit grant with userManager.signInRedirect but without the user getting redirected.
Is there something I should be aware of from a security point here. (You may assume that cross site attacks and antiforgery tokens have been handled).
instead of callign the silent signin after, could I just do a redirect to the implicit flow in the custom authenticate method and have it end up again with the client application ?
Is there something I should be aware of from a security point here.
(You may assume that cross site attacks and antiforgery tokens have
been handled).
My understanding is that you (1) forfeit the ability to participate in Single Sign-on since your browser does not redirect to the SSO Authority, and (2) introduce a weakness in password handling since your client app (both JS/C# in this case) see the password in the plain text.
instead of callign the silent signin after, could I just do a redirect
to the implicit flow in the custom authenticate method and have it end
up again with the client application ?
If you did this, then you would essentially have the authorization_code flow without the authorization code. Might as well just upgrade to the higher security of that flow.
Assuming you have a SPA on top of an ASP.NET MVC app, you could use a traditional MVC form post with a redirect to SSO and then, upon return, spin up the SPA.

Resources