SignInManager::ExternalLoginSignInAsync returns IsNotAllowed for social login - asp.net-core-mvc

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();

Related

How to prevent multiple login to the same account in ASP.NET Core

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.

Create new Parse.Session programmatically for a user, without their password

I'm working on a existing Parse server, and I am currently adding an OAuth system, so that external apps can connect in the name of Parse.User.
I created different classes for codes and tokens, and now my external apps can send requests with an accessToken, corresponding to their application and user (who granted access).
I'm looking for a way to inform the Parse server that the "logged in user" in requests is the end user that authorized the OAuth application. For this, I have created an express middleware handling request before the Parse server middleware, extracting the access token from the request, getting the correct User and Application, and then I wanted to create a Parse.Session programmatically, get the token, and set it in the request as x-parse-session-token. This way, the next handler, Parse, would treat the request as authenticated and performed by the end user.
My problem here is that I cannot find a way to create a session programmatically, I'm aware of the Parse.User.logIn, but that works only with a password.
I've tried the following:
const oAuthSession = await new Parse.Session().save({
user: user // user got from Parse.Query(Parse.User) with masterKey
}, { useMasterKey: true })
But get a Cannot modify readonly attribute user error.
Any hidden method to programmatically create a Parse.Session without a password ?
As pointed out by #DaviMacêdo in the community forum: https://community.parseplatform.org/t/create-new-parse-session-programmatically-for-a-user-without-their-password/1751
We can inject the user directly in the request field, and it will be picked up by Parse: https://github.com/parse-community/parse-server/blob/f6a41729a7a3adc6bd5310cefb3458835b4abb58/src/middlewares.js#L199
const user = await new Parse.Query(Parse.User).get(‘idOfUser’);
req.userFromJWT = user;

Getting Request_ResourceNotFound error when retrieving GraphServiceClient.Me data

I have an ASP.NET Core application with work & school account authentication as configured by Visual Studio 2015 Update 3. I'm trying to integrate Microsoft.Graph with my application. Specifically, I'm trying to obtain user information (name, department, mail, about me, etc.) from the currently logged in user.
Following the samples for previous versions of ASP.NET MVC, I managed to make the following code work.
var token = await GetAppTokenAsync(authStringMicrosoft, graphResourceId);
var authHelper = new AuthenticationHelper() { AccessToken = token };
var graphClient = new GraphServiceClient(authHelper);
var users1 = await graphClient.Users.Request().GetAsync();
var users2 = await graphClient.Users.Request().Select("mail,givenName,surname").GetAsync();
This code is placed on the OnTokenValidated callback of OpenIdConnectEvents within OpenIdConnectOptions, on my Startup class.
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions {
// ...
Events = new OpenIdConnectEvents {
OnTokenValidated = async ctx => {
// right here
}
}
});
So far, both calls to Users work great, and the code retrieves me a list of users with the specified properties. However, whenever I try to get data from the Me property, I get an error, as described below.
User me = await graphClient.Me.Request().GetAsync();
Error:
Code: Request_ResourceNotFound
Message: Resource '65c4885a-b493-4b8d-861f-79f0b8c23ec4' does not exist
or one of its queried reference-property objects are not present.
Inner error
I don't get why am I getting this error. I have checked the permissions for the application in the Azure Management Portal, both for Windows Azure Active Directory and Microsoft Graph applications. As a test, I checked everything that is to check, and still get this error.
So, my question is: why I get this error? Do I need to add a different permission, do I need to include anything else?
Thank you in advance.
From the sample code, it looks like app token is used here. /me request is not valid in context where app token (obtained by client_credential flow) is used. It is valid only in the context of access token obtained by authorization code flow (also referred to as 3-legged flow). If you can share request-id header from the error response along with timestamp, I can confirm.

Silent login using Username in Azure Active Directory in Xamarin

I am developing app using Xamarin Forms. I have created a directory on azure portal. As i see references over internet , active directory authentication uses Microsofts login page to log in.
I want to create native login form and pass user name to active directory and authenticate it.
Is it possible to pass user credentials programatically and authenticate user?
How can i pass user credentials?
I have tried following but i got "(411) Length required" exception
var request = WebRequest.Create(string.Format(#"https://login.microsoftonline.com/{0}/oauth2/token?client_id=5e811f4f-4fa4-451e-a439-ca05cabc02d7&grant_type=password&username=02atul.com#gmail.com&password=userpassword&scope=openid", tenant));
request.ContentType = "application/x-www-form-urlencoded";
request.Method = "POST";
using (HttpWebResponse response = await request.GetResponseAsync() as HttpWebResponse)
{
if (response.StatusCode != HttpStatusCode.OK)
Debug.WriteLine("Error fetching data. Server returned status code: {0}", response.StatusCode);
using (StreamReader reader = new StreamReader(response.GetResponseStream()))
{
var content = reader.ReadToEnd();
if (string.IsNullOrWhiteSpace(content))
{
Debug.WriteLine("Response contained empty body...");
}
else {
Debug.WriteLine("Response Body: \r\n {0}", content);
}
}
}
My username is my email id. Is it correct user name? As i am getting bad request error now. What's missing?
Technically you could use username and password flow which is described in more detail here with ADAL.
However, you need to revisit your scenario and understand if it really accomplishes what the Azure Active Directory Platform is for.
Users use OAuth2 based authentication to get the security and safety of only having to share their passwords with trusted identity providers like MS, FB, Google, etc... In general, the safety conscious person will NOT want to type in a password into your random website, and trust that you do not abuse that information. If you want to use AAD, you should also use our login experiences, as this is really what the customer is paying for in our service in many regards.
EDIT: If ADAL no longer supports this flow, you can simply generate the http requests yourself:
POST: https://login.microsoftonline.com/xxxxx.onmicrosoft.com/oauth2/token
Content-Type: application/x-www-form-urlencoded
resource={resource}&client_id={clientId}&grant_type=password&username={userName}&password={password}&scope=openid&client_secret={clientSecret}

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

Resources