How to prevent multiple login to the same account in ASP.NET Core - asp.net-core-mvc

I'm using ASP.NET Core 6 Identity for login system. I want to prevent multiple login to the same account.
My Identity settings are:
//For custom Identity
string connection = configuration.GetConnectionString("DefaultConnection");
builder.Services.AddIdentity<AppUser, AppRole>(options =>
{
options.User.RequireUniqueEmail = false;
options.Password.RequireDigit = true;
options.Password.RequireUppercase = false;
options.Password.RequiredUniqueChars = 0;
options.Password.RequireUppercase = false;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequiredLength = 6;
options.Lockout.MaxFailedAccessAttempts = 10;
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(15);
options.Tokens.PasswordResetTokenProvider = TokenOptions.DefaultProvider;
}).AddEntityFrameworkStores<IdentityAppContext>().AddDefaultTokenProviders();
I searched some similar questions but none of them could help me to implement this feature in ASP.NET Core 6.
Please guide me.

What is the Use Case for denying access while logged in?
"When someone logged in, no body can log in to that account until he closes the browser or logs out manually"
That would require logic like:
On Login, throw error if tokenHasBeenIssued, by querying the server db.
On Login, if no server token for user, createToken.
On Logout, a clean db, removeUserToken
but, when someone closes their browser there is no message sent to the server, so you'd never clear the token, so you'd get one login granted and then they would be logged out forever.
Maybe this scenario is fixable with a hack of a 'Timed cron job to clear all old tokens'?
I would suggest implement two factor authentication or even delegate your auth needs to third party provider, eg Auth0 or Azure AD, etc.

If you mean to stay signed in, you need to implement a token(for example, JWT Token) and use User ID or username directly without logging in again.

Related

How to know if a given user is already logged in with MSAL?

With msal.js library (The Microsoft Authentication Library), which is the way to know if a given user is already logged in? My intention is to avoid to show login pop-up if the user's credentials are already saved in browser's storage
My current approach:
function isUserLoggedIn(username) {
const agent = msal.UserAgentApplication(...);
const user = agent.getUser();
return user != null && user.displayableId === username);
}
But I'm not sure if I have to check if the user credentials are outdated/expired. Which is the proper way to go?
With the MSAL agent instance, you can get user information because it is cached. Getting information (such as the userId) doesn't mean that the user's credentials are still valid (logged in).
To be 100% sure that the user is logged in, ask for a token
const promise = agent.acquireTokenSilent(...)
If the user is not logged in, the promise will be rejected with the error code user_login_error
If, on the other hand, the user is still logged in, the promise will be resolved
From MSAL samples, they were checking this way:
let isLoggedIn = this.authService.instance.getAllAccounts().length > 0;

Auth0 and Google API, access token expired, howto write files to Google Drive

I've written an online converter and integrated Auth0 to my website. What I'm trying to achieve is to auto-upload the converted file to the Google Drive of a logged in user. I set up Google oauth in Auth0 and everything seemed to work fine.
The problem is, Google's access_token expires after 60min and I don't have a refresh_token. Therefore, the user needs to log in via the Google Login-page again. That is not, what I want, because the user is in fact logged in way longer than just 60min on my site, but Google refuses API-calls (because the Google token expired).
I know I can request a refresh_token by setting access_type=offline but this will add the permission Have offline access. I don't want that, I just want to upload data to the user's Drive, if he clicked the convert button on my page. I don't want to ask the users for permissions I don't need. If I (as a user) would link my Google account on a similar page and the tool asks for offline access I wouldn't approve, to be honest - the permission sounds like the tool creator can do whatever he wants with your account whenever he wants... There are many tools out there that have write access to a user's Drive without asking for offline access and with one single login until the user revokes the permission. How is that done?
Is there a way to make Google API calls without asking for offline access and without forcing the user to approve the app (that is already approved by him) again and again every 60min?
Thanks in advance,
phlo
Is there a way to make Google API calls without asking for offline access and without forcing the user to approve the app (that is already approved by him) again and again every 60min?
Yes there are ways, but it depends on the specifics of your use case. For example, is your code in Java/php/etc running on a server, or is it JavaScript running in the browser?
Running your auth in the browser is probably the simplest solution as the Google library (https://developers.google.com/api-client-library/javascript/features/authentication) does all the work for you.
By asking for offline access you are requesting a refresh token. Google is going to tell the user that you are requesting offline access. You can request something without telling the user what they are authorizing.
No there is no way to request a refresh token without displaying that message. Nor is there a way for you to change the message it's a standard Google thing.
I found the solution!
Prerequirements
Enable Use Auth0 instead of the IdP to do Single Sign On in your client's Dashboard
Create a new Angular-route to handle the silent login callback (e.g. /sso)
Add
$rootScope.$on("$locationChangeStart", function() {
if ($location.path().indexOf("sso") == -1) {
authService.relogin(); //this is your own service
}
});
to your run-function and set the callbackURL in angularAuth0Provider.init() to your new Angular-route (<YOUR_DOMAIN>/sso). Add this URL to your accepted callbacks in the Auth0 dashboard - this won't end in an infinite loop, because the locationChangeStart-event won't call authService.relogin() for this route
Add $window.close(); to the controller of the Angular-route (/sso) to auto-close the popup
Authenticate the user via Auth0 and save the timestamp and the Auth0-access_token somewhere
On reload:
Check, if the Auth0-token is still valid in authService.relogin(). If not, the user has to login again either way. If the token is valid and the Google token is about to expire (check this with the saved timestamp to prevent unnecessary API calls) check for SSO-data and login silently, if present
/* ... */
if (validToken && googleExpired) {
angularAuth0.getSSOData(function (err, data) {
var lastUsedConnection = data.lastUsedConnection;
var connectionName = (_.isUndefined(lastUsedConnection) ? undefined : lastUsedConnection.name);
var isGoogle = (_.isUndefined(connectionName) ? false : connectionName == "google-oauth2");
if (!err && data.sso && isGoogle) {
authManager.authenticate();
localStorage.setItem("last-relogin", new Date().getTime());
angularAuth0.signin({
popup: true,
connection: data.lastUsedConnection.name
});
}
});
}
Now you will find a fresh Google access_token for this user (without asking for offline access)
Resources:
https://auth0.com/docs/quickstart/spa/angularjs/03-session-handling
https://auth0.com/docs/quickstart/spa/angularjs/11-sso

SignInManager::ExternalLoginSignInAsync returns IsNotAllowed for social login

I am using Microsoft.NetCore.App v1.1.0 (Nov 16, 2016) and have built a standard MVC Web App to which I've added Social Login for Microsoft and Google following advice given at Microsoft Docs.
During the first login using Google (or Microsoft) it works as expected - i.e. following authentication of my Google account my WebApp presents me with a Registration webpage requiring me to provide an email address for association with my Google account and then I'm logged into my WebApp. Unfortunately, the same happens on the second login, so obviously registration fails as the email address is already in my AspNetUsers table.
I've put a break point on _signInManager.ExternalLoginSignInAsync() in AccountController::ExternalLoginCallback(). During first login it returns Result.Succeeded false, presumably because the email isn't registered. During second login it returns Result.IsNotAllowed which isn't handled by the following series of 'if' statements so gets handled by the 'else' block - same as for Result.Succeeded false.
Does anyone know what Result.IsNotAllowed means and why it is being set? Is there a workaround?
Thanks Will
I was just having troubles with the same thing. You probably have the following line in startup.cs:
services.AddIdentity(config => config.SignIn.RequireConfirmedEmail = true)
If you have a look at Identity source code on github you will see that IsNotAllowed is returned when email has not been confirmed yet. It does not matter if this is a third party provider or a local account. To fix this, just set EmailConfirmed = true when you create and store the ApplicationUser. In the template project that is ExternalLoginConfirmation method.
var user = new ApplicationUser { UserName = model.Email, Email = model.Email, EmailConfirmed = true};
There's actually a simpler workaround, if you want to confirm the user email coming from the social login provider at some point instead for automatically marking it as confirmed. You can retrieve the user after registering it and flag the email as confirmed, without saving this to the data repository.
var user = await _userManager.FindByLoginAsync(info.LoginProvider, info.ProviderKey);
if (!user.EmailConfirmed)
{
user.EmailConfirmed = true;
// Don't save this to the DB
}
var signInResult = await _signInManager.ExternalLoginSignInAsync();

Missing Claims and Identity Info with IdentityServer v3

I have IdentityServer with Membership Reboot and IdentityManager running on a remote server, I've used the Admin UI of IdentityManager to setup a user, and add roles & claims to said user.
I'm developing a WebApi/SPA project that will use the remote server for Auth. Using fiddler I can request a token from the IdentityManagner on the remote box and use this token to against the local WebApi where Authorization is required. If the token is valid the WebApi processes like normal, if the token is bogus I get a 401. Works great.
The problem is when I want additional information about the user none of the claims or identity information is coming across. I'm not sure if the problem is at the IdentityServer side, The WebApi side, or if I'm not doing something correctly when getting my token.
I didn't realize we needed put the claims in the Scope definition. Incase anyone else stumbles upon this I changed my scope to the following
var scopes = new List<Scope>
{
new Scope
{
Enabled = true,
Name = "publicApi",
Description = "Access to our public API",
Type = ScopeType.Resource,
IncludeAllClaimsForUser = true, //I'll filter this down later
}
};
scopes.AddRange(StandardScopes.All);
return scopes;
Further details can be found here

How to reset google oauth 2.0 authorization?

I'm using Google APIs Client Library for JavaScript (Beta) to authorize user google account on web application (for youtube manipulations). Everything works fine, but i have no idea how to "logout" user from my application, i.e. reset access tokens.
For example, following code checks user authorization and if not, shows popup window for user to log into account and permit web-application access to user data:
gapi.auth.authorize({client_id: CLIENT_ID, scope: SCOPES, immediate: false}, handleAuth);
But client library doesn't have methods to reset authorization.
There is workaround to redirect user to "accounts.google.com/logout", but this
approach is not that i need: thus we logging user off from google account not only from my application, but also anywhere.
Google faq and client library description neither helpful.
Try revoking an access token, that should revoke the actual grant so auto-approvals will stop working. I assume this will solve your issue.
https://developers.google.com/accounts/docs/OAuth2WebServer#tokenrevoke
Its very simple. Just revoke the access.
void RevokeAcess()
{
try{
HttpClient client = new DefaultHttpClient();
HttpPost post = new HttpPost("https://accounts.google.com/o/oauth2/revoke?token="+ACCESS_TOKEN);
org.apache.http.HttpResponse response = client.execute(post);
}
catch(IOException e)
{
}
}
But it should be in asyncTask
It depends what you mean by resetting authorization. I could think of a three ways of doing this:
Remove authorization on the server
Go to myaccount.google.com/permissions, find your app and remove it. The next time you try to sign in you have to complete full authorization flow with account chooser and consent screen.
Sign out on the client
gapi.auth2.getAuthInstance().signOut();
In this way Google authorization server still remembers your app and the authorization token remains in browser storage.
Sign out and disconnect
gapi.auth2.getAuthInstance().signOut();
gapi.auth2.getAuthInstance().disconnect();
This is equivalent to (1) but on the client.
Simply use: gapi.auth.setToken(null);
Solution for dotnet, call below API and pass the access token, doc - https://developers.google.com/identity/protocols/oauth2/web-server#tokenrevoke
string url = "https://accounts.google.com/o/oauth2/revoke?token=" + profileToken.ProfileAccessToken;
RestClient client = new RestClient(url);
var req = new RestRequest(Method.POST);
IRestResponse resp = client.Execute(req);

Resources