ASP.NET Core 5: Best way to use common URL for login page and root page - asp.net

In an ASP.NET Core 5 web app with Identity (and in earlier versions), the URL for the login page defaults to:
https://[yourhost]/account/login
and once you're logged in, the root of your project lives at:
https://[yourhost]
But in many (most?) web apps, the login page shares the URL with the root page. Using Facebook as an exmaple, before I'm logged in, the URL is https://www.facebook.com, and after logging in the URL is the same. What's the best way to achieve this in ASP.NET Core?
The best solution I have so far is:
(AccountController.cs)
[HttpGet]
[AllowAnonymous]
[Route("/")]
public async Task<IActionResult> Login(string returnUrl = null)
{
// If authenticated, serve the application page.
if (User.Identity.IsAuthenticated)
{
return View("~/Views/Weather/Index.cshtml");
}
// Otherwise, serve the login page.
else
{
ViewData["ReturnUrl"] = returnUrl;
return View();
}
}
This doesn't feel great because , from AccountController, I'm returning a view that should correspond to WeatherController.
Ideally, I'd like to say, "for URL '/' , only if the user is logged in, match this endpoint in the weather controller. Else, fall back to this other endpoint in the account controller." I was thinking this might be possible with a custom route constraint, but I'm not necessarily passing any parameters to the URL. I was also looking into DynamicRouteValueTransformer, but wasn't successful.
Or, is it misguided for me to try to have the login page share a URL with the root page? Thanks for any suggestions.

Change Default Settings of Identity Framework,
services.ConfigureApplicationCookie(options =>
{
// Cookie settings
options.Cookie.HttpOnly = true;
options.ExpireTimeSpan = TimeSpan.FromMinutes(5);
options.LoginPath = "/Home/Index/; // here
options.AccessDeniedPath = "/Identity/Account/AccessDenied";
options.SlidingExpiration = true;
});
after that check, if the user is authenticated return a different view, and if the user is not authenticated return a different view that user can login.

Related

Best practice: Redirecting to client error page on failed identity server login

My security architecture consists of an Identity Server, JS Client and an external provider. The Identity Server uses authorization code flow.
A simplified login process looks as follows: User clicks login in the JS Client -> redirected to Identity Server -> redirected to external provider. I then use the external provider to authenticate the user. When sucessfully redirected back to my Identity Server I then check in a database if the user is active or not. This happens in the AccountController:
[HttpGet]
[Route("callback")]
public async Task<IActionResult> Callback()
{
var result = await HttpContext.AuthenticateAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme);
...
var returnUrl = result.Properties.Items["returnUrl"] ?? "~/";
// My check if user is active or not
var isActive = await IsActiveAsync(providerUserId);
if (!isActive)
{
var errorRedirectUri = "https://127.0.0.1/error_page";
return Redirect(errorRedirectUri);
}
var context = await Interaction.GetAuthorizationContextAsync(returnUrl);
await Events.RaiseAsync(new UserLoginSuccessEvent(provider, providerUserId, providerUserId, subjectId, true, context?.Client.ClientId));
return Redirect(returnUrl);
}
If the user is active, the flow works perfectly. Access and refresh tokens are created, user is signed into the Identity Server and redirected back to the client. I am having issues in handling the case, where the user sucessfully logged in to the external provider but is not active in my database. In this case I want to redirect to a error page of the client.
An obvious way to do it, is to configure a error redirect uri, to which the user is redirected to in the mentioned scenario. But this does not feels like the proper way.
I also considered implementing the ProfileService.IsActiveAsync(...) method, but this just resulted in redirecting the user back to the external provider login after setting context.IsActive = false;. With no option of returning to the JS Client without valid credentials of an active user.
public async Task IsActiveAsync(IsActiveContext context)
{
// My check if user is active or not
context.IsActive = await IsActiveAsync(context.Subject);
}
So my question is: What is the apropriate way of redirecting the user to a client-side error page when the user was sucessfully authenticated at the external provider but was not marked as active in my own database?

Getting URL parameter while using social login in ASP.NET Core app

I'm using social media logins in my ASP.NET Core app. The whole app except for a few pages, requires users to be authenticated.
I have a landing page that allows anonymous users and does a redirect to the login page and it adds a URL parameter i.e. myapp.com/login?id=someid12345
I have three actions in my controller that allow users to login using their social media accounts. The first is just the basic page that renders login buttons. In this first step, I can capture the Id pretty easily.
[AllowAnonymous]
public IActionResult Login([FromQuery] string id)
{
// So I can capture the Id here
}
Once the user clicks the social media login of his choice, we come to this action:
[AllowAnonymous]
public async Task ExternalLogin(string provider, string returnUrl, string myId)
{
var properties = new AuthenticationProperties
{
RedirectUri = "Login/Callback"
};
// Add returnUrl to properties -- if applicable
if (!string.IsNullOrEmpty(returnUrl) && Url.IsLocalUrl(returnUrl))
properties.Items.Add("returnUrl", returnUrl);
// Add url param Id to properties -- if applicable
if (!string.IsNullOrEmpty(myId))
properties.Items.Add("myId", myId);
await HttpContext.Authentication.ChallengeAsync(provider, properties);
return;
}
My question is, how do I get my Id in the callback action?
[AllowAnonymous]
public async Task<IActionResult> Callback()
{
// I can get user info here once authenticated by social media provider
var user = HttpContext.User;
// How do I get myId?
}
You can use state parameter for this purpose. State parameter helps to avoid CSRF attacks in OAuth workflow. The state parameter according to the Google OAuth 2.0 docs - Provides any state that might be useful to your application upon receipt of the response. The Google Authorization Server round-trips this parameter, so your application receives the same value it sent. Possible uses include redirecting the user to the correct resource in your site, and cross-site-request-forgery mitigations - https://developers.google.com/identity/protocols/OAuth2UserAgent. This should work in Facebook as well.

Implementing active authentication using ADFS

I am working on the authentication with Active Directory using ADFS.
While searching, I got few articles to accomplish this requirement, but they are suggesting to redirect the Login page of application to Login page of ADFS and then come back.
Redirecting to ADFS Login page is not suggested as per user experience.
Can anyone help me to find out the solution to authenticate with active directory using ADFS behind the scene ? So, everything will be handled by application code, not by ADFS login page.
Please advise.
Please let me know if you have any concern or query or if you need more information.
The reason those articles suggest you redirect (using WS-Federation protocol) to the ADFS login page is because it allows you to set up federation to other identity providers (allow an external company' employees to use their own credentials to log in to your application).
What you want can be done using the WS-Trust protocol, but you'll give up (or have to implement yourself) the possibility to federate.
ADFS exposes endpoints like /adfs/services/trust/13/usernamemixed that you can talk to to get a security token. Something like below should get you going.
public class UserNameWSTrustBinding : WS2007HttpBinding
{
public UserNameWSTrustBinding()
{
Security.Mode = SecurityMode.TransportWithMessageCredential;
Security.Message.EstablishSecurityContext = false;
Security.Message.ClientCredentialType = MessageCredentialType.UserName;
}
}
private static SecurityToken GetSamlToken(string username, string password)
{
var factory = new WSTrustChannelFactory(new UserNameWSTrustBinding(), "https://yourdomain.com/adfs/services/trust/13/UsernameMixed")
{
TrustVersion = TrustVersion.WSTrust13
};
factory.Credentials.UserName.UserName = username;
factory.Credentials.UserName.Password = password;
var rst = new RequestSecurityToken
{
RequestType = RequestTypes.Issue,
AppliesTo = new EndpointReference("https://yourdomain.com/yourservice"),
KeyType = KeyTypes.Bearer
};
var channel = factory.CreateChannel();
return channel.Issue(rst);
}

using asp.net authentication with custom authentication

Here is my Scenario.
I have authentication web-services exposed by another domain. Now I want user name and password to be sent to that external domain for authentication. and when user is authenticated (returned true), I want the ASP.net to take that authentication further and let the user in and provide me all the asp.net standard utilities accessible, like currentuser, Isauthorized, Roles etc, for the user, authenticated. I hope this make sense.
This is not a problem. You have a variety of options available to you. One approach is to blend Forms Authentication with your own security model.
The basic idea is to let Forms Auth create and manage a ticket (in the form of an encrypted ticket) for the logged-in user. The ticket is used to determine whether or not someone is logged in, and who they are. You can then mix in any additional security related logic on top of that.
To process the login request, you just have a controller and action like you normally would. Note: in the example below, I am making some assumptions about LoginViewModel, the service you are using to authenticate, and the object it returns if any. You'll have to sub in your actual logic.
public ActionResult Login(LoginViewModel model)
{
// make sure the user filled out the login fields correctly
if (!ModelState.IsValid) return View(model);
// authenticate the user here
var authenticatedUser = AuthorizeUserUsingRemoteWebService(model.Username, model.Password);
if (authenticatedUser.IsAuthenticated)
{
// create forms auth ticket cookie and redirect to the home page
FormsAuthentication.SetAuthCookie(authenticatedUser.Username);
return RedirectToAction("Index", "Home");
}
// authentication failed, so show the login page again
return View(model);
}
In addition to that, you may have an HTTP module that handles the AuthenticateRequest event. Your module will be registered after the Forms Auth HTTP module, so it will have already processed whether or not the user is logged in. What you want to do is look up additional information if they are logged in, to get roles and such.
public class CustomAuthHttpModule : IHttpModule
{
public void Init(HttpApplication context)
{
context.AuthenticateRequest += new EventHandler(OnAuthenticateRequest);
}
void OnAuthenticateRequest(object sender, EventArgs e)
{
HttpApplication application = (HttpApplication)sender;
HttpContext context = appObject.Context;
// user isn't logged in, so don't do anything else
if (!context.User.Identity.IsAuthenticated) return;
// look up the roles for the specified user, returning the role names as an array of strings
string[] roles = LookupUserRolesFromWebService(context.User.Identity.Name);
// replace the current User principal with a new one that includes the roles we discovered for that user.
context.User = new GenericPrincipal(new GenericIdentity(context.User.Identity.Name), roles);
}
}
You'll register the HTTP module in your web.config:
<httpModules>
<add name="CustomAuthHttpModule"
type="MyAssembly.CustomAuthenticationModule, MyAssembly" />
</httpModules>
You can now use the User object in your MVC controllers and views, the AuthenticatedAttribute, etc.
However, I'd recommend that you cache the results of looking up a user's roles so you don't hammer your web service. I'll leave that up to you.
You can you use Security Token Service for your application. Setup a Windows Identity Foundation SDK and find examples in sdk directory (for me it is "C:\Program Files (x86)\Windows Identity Foundation SDK\v4.0\Samples\End-to-end\Federation for Web Apps"). One of them ( named "Federation for Web Apps") implement your case for AD authentication.

How to use DotNetOpenAuth to login to websites?

I want to do is, if the users are logged into gmail and if they go to my website they automatically get logged in.
I am doing it in the following way... maybe there is a better way of doing it.
In my website I have a place for uses to give their gmail address so my website knows gamil address of the registered user.
So when they go to my website I want to know whether they are logged into gmail and what is their gmail address.
How should I find this information using DotNetOpenAuth?
I found following code from the web and it is authenticating the user. But i have to press the button and go to gmail login every time.
if the user is already using gmail I don’t have to ask the user for login i can use it.
How do i modify this code to achieve that?
static string openidurl = "https://www.google.com/accounts/o8/id";
protected void Page_Load(object sender, EventArgs e)
{
//The Response
OpenIdRelyingParty openid = new OpenIdRelyingParty();
var response = openid.GetResponse();
if (response != null)
{
switch (response.Status)
{
case AuthenticationStatus.Authenticated:
var fetch = response.GetExtension<FetchResponse>();
string email = "";
if (fetch != null)
{
email = fetch.GetAttributeValue(WellKnownAttributes.Contact.Email);
}
break;
}
}
}
protected void Button1_Click(object sender, EventArgs e)
{
using (OpenIdRelyingParty openid = new OpenIdRelyingParty())
{
IAuthenticationRequest request = openid.CreateRequest(openidurl);
var fetch = new FetchRequest();
fetch.Attributes.AddRequired(WellKnownAttributes.Contact.Email);
request.AddExtension(fetch);
// Send your visitor to their Provider for authentication.
request.RedirectToProvider();
}
}
It sounds like what you're asking for is "single-sign-on", where a visitor to your site who is already logged into Google is immediately logged into your site when they first visit it, rather than after clicking a "Google Login" button on your site.
The short answer is you can't do this. The longer answer is that you can get close.
The first and hard restriction is that first-time visitors to your site will never get automatically signed in, because Google and the user don't yet trust your site. Every user has to explicitly log in once, with Google asking the user "do you want to log into this site and remember this choice?" If they say yes, then in the future when the user is already logged into Google and visits your site, they can click the Google Login button on your site and they'll never see Google -- they'll just be immediately logged into your site.
So the next question is how do you remove the requirement on the user to click "google Login". You can accomplish this by when an unauthenticated user visits your site, you can immediately redirect them to your log in page, which will immediately initiate the "Google Login" flow (the OpenIdRelyingParty.CreateRequest(google).RedirectToProvider() call), using "immediate mode". This will fail if the user isn't logged into Google and trust your site, but the impact will be the user won't see a Google login screen if they do trust your site, but will rather be immediately logged in.
You might find my answer useful: What OpenID solution is really used by Stack Overflow?
I've also made a simple blog post about it: http://codesprout.blogspot.com/2011/03/using-dotnetopenauth-to-create-simple.html
My examples are with MyOpenID, but gmail should work the same way. The OpenID provider basically takes care of the log in, including the case when they're already logged in with the provider.
Update:
In an ASP.NET (in this case ASP.NET MVC) application you would create a cookie when the user is successfully logged in and you would check the cookie to determine if the user is logged in. As I said, please see the links above for detailed code examples and an explanation of how it all works. Here are two code samples from the Controller where I demonstrate how to check if the user is logged in:
// **************************************
// URL: /User/LogIn
// **************************************
public ActionResult LogIn()
{
if (User.Identity.IsAuthenticated) // <--- CHECKS IF THE USER IS LOGGED IN
{
return RedirectToAction("Profile", "User");
}
Identifier openID;
if (Identifier.TryParse(Request.QueryString["dnoa.userSuppliedIdentifier"], out openID))
{
return LogIn(new User { OpenID = openID }, Request.QueryString["ReturnUrl"]);
}
else
{
return View();
}
}
[HttpPost]
public ActionResult LogIn(User model, string returnUrl)
{
string openID = ModelState.IsValid?model.OpenID:Request.Form["openid_identifier"];
if (User.Identity.IsAuthenticated)//<--- CHECKS IF THE USER IS LOGGED IN
{
return RedirectToAction("Profile", "User");
}
else if (!string.IsNullOrEmpty(openID))
{
return Authenticate(openID, returnUrl);
}
else if(ModelState.IsValid)
{
ModelState.AddModelError("error", "The OpenID field is required.");
}
// If we got this far, something failed, redisplay form
return View(model);
}

Resources