How do you create a new user with Membership in MVC 5 - angularjs

I have created a small demo in angularjs with mvc c# and I want to have a login facility using membership provider. I have tried this simple code for the getting records from the database but it doesn't return any users from the database. There are users in the database.
The membership setting in web.config file:
<membership defaultProvider="AspNetSqlMembershipProvider">
<providers>
<clear />
<add name="AspNetSqlMembershipProvider" type="System.Web.Security.SqlMembershipProvider" connectionStringName="DefaultConnection" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" requiresUniqueEmail="true" maxInvalidPasswordAttempts="50" minRequiredPasswordLength="6" minRequiredNonalphanumericCharacters="0" passwordAttemptWindow="10" applicationName="/" />
</providers>
</membership>
then I call this method to get all users:
Membership.GetAllUsers()
Does anyone know what the problem might be? Thanks.

If you have an Azure subscription, then I would recommend using Azure AD B2C. This allows you to create an Azure AD instance specifically for your app(s). They support Azure AD accounts and you can configure additional identity providers like Google, Facebook, MS Personal Account (outlook.com), Linkedin, etc.
It allows you to define custom profile properties in the portal and enabled you to configure the layout / experience for 'New User SignUp', 'Edit My Profile', 'Password Reset', and others.
If you're using Azure AD B2C and ASP.NET Identity in your application, you do not need to sign-up a user yourself. However, if you want access to a new user's data or claims after sign-up, you should be able to access it by modifying the AccountController.cs
In my AccountController.cs:
public void SignUpSignIn(string redirectUrl = "")
{
// this redirect url is where the user is routed after authentication.
var default_redirectUrl = "/account/gateway";
if (string.IsNullOrWhiteSpace(redirectUrl))
{
redirectUrl = default_redirectUrl;
}
// Use the default policy to process the sign up / sign in flow
HttpContext.GetOwinContext().Authentication.Challenge(new AuthenticationProperties { RedirectUri = redirectUrl });
return;
}
// this is where the user is redirected after sign-up
public async Task<ActionResult> Gateway()
{
// if you cast the User.Identity as
// System.Security.Claims.ClaimsIdentity then you can access
// the claims from the identity provider.
var userClaims = (User.Identity as ClaimsIdentity).Claims;
var issuer_claim = userClaims.FindClaim("iss")?.Value;
var nameid_claim = userClaims.FindClaim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier")?.Value;
var audience_claim = userClaims.FindClaim("aud")?.Value;
var auth_time_claim = userClaims.FindClaim("auth_time")?.Value;
var idprovider_claim = userClaims.FindClaim("http://schemas.microsoft.com/identity/claims/identityprovider")?.Value;
var displayname_claim = userClaims.FindClaim("name")?.Value;
var objectid_claim = userClaims.FindClaim("http://schemas.microsoft.com/identity/claims/objectidentifier")?.Value;
var country_claim = userClaims.FindClaim("country")?.Value;
var newUser_claim = userClaims.FindClaim("newUser")?.Value;
var emails_claim = userClaims.FindClaim("emails")?.Value;
// Then you can use this claims as needed.
// after this navigate to your homepage or whatever makes sense.
return RedirectToAction("Index", "Home");
}
I hope this helps.
Link / More Info: https://azure.microsoft.com/en-us/services/active-directory/external-identities/b2c/

Related

Graph API with ASP.NET Core Blazor WebAssembly

I would like to get information from Microsoft graph web API. I followed these instructions:
https://learn.microsoft.com/en-us/aspnet/core/blazor/security/webassembly/graph-api?view=aspnetcore-5.0
The problem is that the variable "token" in the AuthenticateRequestAsync method is always null. It means that the Blazor app does not get the token.
public async Task AuthenticateRequestAsync(HttpRequestMessage request)
{
var result = await TokenProvider.RequestAccessToken(
new AccessTokenRequestOptions()
{
Scopes = new[] { "https://graph.microsoft.com/User.Read" }
});
if (result.TryGetToken(out var token))
{
request.Headers.Authorization ??= new AuthenticationHeaderValue(
"Bearer", token.Value);
}
}
The Program.cs has the following code:
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
builder.Services.AddMsalAuthentication<RemoteAuthenticationState, RemoteUserAccount>(options =>
{
options.ProviderOptions.DefaultAccessTokenScopes.Add("https://graph.microsoft.com/User.Read");
builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
});
builder.Services.AddGraphClient("https://graph.microsoft.com/User.Read");
In Index.razor I just add two lines of code I OnInitializedAsync method
var request = GraphClient.Me.Request();
user = await request.GetAsync();
I spent a lot of time to figure out what is the main issue but without success. I will appreciate any help.
Please imagine the single-page website. Usually, this kind of page has a "contact us" tab where is the contact form. If the user fills up the contact form then data have to be somehow sent to us. For this purpose, I tried to use MS graph API. When the user clicks the submit button, in the background the registration to my account will be created and an email will be sent to me. It means that the user is not aware of any registration procedure. – Samo Simoncic
For your app to be able to create users in a tenant, it needs to use an app only flow which requires a secret. We do not advise exposing app only flows of this nature, which can easily be exploited to create bogus users or overwhelm your tenant, open to the general public.
The best approach would be to take this registrations in a local DB, and then have a daemon app process them behind the scenes. Here is the sample where daemon console application is calling Microsoft Graph.
Not sure about the cause of the issue.
But I can make it work with the following code and configuration:
Program.cs
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("app");
// Adds the Microsoft graph client (Graph SDK) support for this app.
builder.Services.AddMicrosoftGraphClient("https://graph.microsoft.com/User.Read");
// Integrates authentication with the MSAL library
builder.Services.AddMsalAuthentication(options =>
{
builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
options.ProviderOptions.DefaultAccessTokenScopes.Add("https://graph.microsoft.com/User.Read");
});
await builder.Build().RunAsync();
}
appsettings.json
{
"AzureAd": {
"Authority": "https://login.microsoftonline.com/exxxxx4e-bd27-40d5-8459-230ba2xxxxxb",
"ClientId": "7xxxxxx8-88b3-4c02-a2f8-0a890xxxxxx5",
"CallbackPath": "/signin-oidc",
"ValidateAuthority": "true",
"DefaultScopes": [
"openid",
"profile"
]
}
}
You can refer to the configuration and sample code here.
I have cloned your repo from the GitHub URL you posted in the comments.
There is no issue with the code to fetch the data from the Microsoft Graph API, the problem is that you have written the code of calling the API when the apps shows the index component before even the user logs in, you have to check if the user is logged in first and add a login button to the UI or you can add [Authorize] to the index page so it will redirect the user to Login before it shows the component and make the API and to implement that make sure to add the CascadingAuthenticationState and AuthorizeView to your App.razor as following
<CascadingAuthenticationState>
<Router AppAssembly="#typeof(Program).Assembly">
<Found Context="routeData">
<AuthorizeRouteView RouteData="#routeData" DefaultLayout="#typeof(MainLayout)">
<NotAuthorized>
#if (!context.User.Identity.IsAuthenticated)
{
<a class="btn btn-success" href="/authentication/login">Login with Microsoft</a>
}
else
{
<p>You are not authorized to access this resource.</p>
}
</NotAuthorized>
</AuthorizeRouteView>
</Found>
<NotFound>
<LayoutView Layout="#typeof(MainLayout)">
<p>Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
</CascadingAuthenticationState>
And then in your Index.razor add at the top the following line
#attribute [Authorize]
Then you launch the app if the user is not logged in, he/she will be asked to do so and then go to the Index component and make the API call which will succed then

Ignore multi-factor authentication in azure graph API request

I want to validate user credential from Azure AD. It works for users who haven't enable MFA.but MFA enabled users getting below error.
Due to a configuration change made by your administrator, or because
you moved to a new location, you must use multi-factor authentication
to access
So it need a way to ignore MFA ,when we accessing though the graph API
this is my code.
var values = new Dictionary<string, string>
{
{ "grant_type", "password" },
{ "client_secret", appKey },
{ "client_id", clientId },
{ "username", userName },
{ "password", password },
{ "scope", "User.Read openid profile offline_access" },
};
HttpClient client = new HttpClient();
string requestUrl = $"https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token";
var content = new FormUrlEncodedContent(values);
var response = client.PostAsync(requestUrl, content).Result;
if (response.IsSuccessStatusCode)
{
return true;
}
else
{
return false;
}
The correct way to validate user credentials is have the user authenticate interactively through a browser.
This will allow them to go through MFA, login through federation with ADFS etc.
And most importantly, the users do not have to give their password to your app.
The flow you are trying to use only exists in the spec as an upgrade path for legacy applications.
Its usage becomes essentially impossible once MFA is enabled.

How to set acr-values for Sustainsys external provider in identity server 3

I have Idsvr3 with local user accounts in SQL. In addition i have also configured external identity provider which support SAML2 using https://github.com/Sustainsys/Saml2 I followed the sample here
Now when user access the client application he gets redirected to login page which presents userid/password textboxes for local login and also a button to redirect to external provider.
I want to change this behavior. I want user directly goto external login based on some condition. I've read that I can pass the required login provider to the acr_values and IdSvr3 will directly go to external provider.
Here is how i registered external provider with IdSvr3 (Note some code is removed for brevity)
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.Map("/identity", idsrvApp =>
{
var identityServerOptions = new IdentityServerOptions
{
AuthenticationOptions = new AuthenticationOptions()
{
}
.Configure(ConfigureIdentityProviders),
};
idsrvApp.UseIdentityServer(identityServerOptions);
});
}
private void ConfigureIdentityProviders(IAppBuilder app, string signInAsType)
{
// SAML2
var options = new Saml2AuthenticationOptions(false)
{
SPOptions = new SPOptions
{
EntityId = new EntityId("https://localhost:44300/IdSrv3/Saml2"),
},
SignInAsAuthenticationType = signInAsType,
Caption = "SAML2p"
};
UseIdSrv3LogoutOnFederatedLogout(app, options);
options.SPOptions.ServiceCertificates.Add(new X509Certificate2(
AppDomain.CurrentDomain.SetupInformation.ApplicationBase + "/App_Data/Sustainsys.Saml2.Tests.pfx"));
options.IdentityProviders.Add(new IdentityProvider(
new EntityId("https://stubidp.sustainsys.com/Metadata"),
options.SPOptions)
{
LoadMetadata = true
});
app.UseSaml2Authentication(options);
}
}
and here is client application startup
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.UseCookieAuthentication(CK);
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
Authority = "https://localhost:44300/identity",
Scope = "openid profile email",
ClientId = "XXXXXXXXXXXXXXX",
RedirectUri = "http://localhost:36102/",
ResponseType = "id_token",
SignInAsAuthenticationType = "Cookies",
Notifications = new OpenIdConnectAuthenticationNotifications
{
RedirectToIdentityProvider = (n) =>
{
if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.AuthenticationRequest)
{
if(SomeCondition == true)
{
n.ProtocolMessage.AcrValues = "idp:saml2";
}
}
return Task.FromResult(0);
}
}
});
}
}
However identity server throws error External login error: provider requested saml2 is not a configured external provider
What is the valid name for Sustainsys/Saml2 provider and where is it configured?
I think i found it. The idp is actually the value of AuthenticationType property.
During external provider setup in IdentityServer3, the Saml2AuthenticationOptions by default sets the AutheticationType to Saml2.
So in client application i have to use exact same value as acr-values, it is case-sensitive. I was using small s instead of capital S. When i changed to Saml2 it worked.
I can also override AutheticationType to any string i want, and that is good because now i can setup multiple external IdP that supports SAML2 protocol and differentiate them by their AutheticationType
Also i found this documentation helpful
https://media.readthedocs.org/pdf/saml2/latest/saml2.pdf
Take a look how okta is configured with IdentityServer3 in section 2.5.4 Step 3: Configure your identity server with the new identity provider
Also from IdentityServer documentation
AuthenticationType must be a unique value to identify the external
identity provider. This value will also be used for the idp claim in
the resulting tokens. Furthermore the same value can be used to
pre-select identity providers during authorization/authentication
requests using the acr_values parameter (see this for more
information). This value is also used to restrict the allowed identity
providers on the Client configuration.

What is the right way to combine internal auth along with B2C auth for the same Azure web app?

I have an application web MVC that uses AAD to authenticate, But I need the same application to be used as B2C.
Is there any way to configure it or should I create a page for the user to choose by which directory to authenticate.
Is there any way to configure it or should I create a page for the user to choose by which directory to authenticate.
Based on my understanding, we need to provide a way to provide users to choose the identity data provider they want to authentication if the web app provide multiple identity data providers. For example, we can provide two buttons or a custom login page as you mentioned in above.
Here is a piece of custom code which provide users select the login way using two buttons from the code sample here.
_LoginPartial.cshtml(Add the UI to choose identity data provider):
<ul class="nav navbar-nav navbar-right">
<li>#Html.ActionLink("Sign up", "SignUp", "Account", routeValues: null, htmlAttributes: new { id = "signUpLink" })</li>
<li>#Html.ActionLink("Sign in", "SignIn", "Account", routeValues: new { b2c=false}, htmlAttributes: new { id = "loginLink" })</li>
<li>#Html.ActionLink("Sign in B2C", "SignIn", "Account", routeValues: new { b2c = true }, htmlAttributes: new { id = "loginLink" })</li>
</ul>
Startup.Auth.cs(Provider two identity data providers)
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
// Configure OpenID Connect middleware for each policy
app.UseOpenIdConnectAuthentication(CreateOptionsFromPolicy(SignUpPolicyId));
app.UseOpenIdConnectAuthentication(CreateOptionsFromPolicy(ProfilePolicyId));
app.UseOpenIdConnectAuthentication(CreateOptionsFromPolicy(SignInPolicyId));
//config for normal azure ad
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = "{AzureADClientId}",
Authority = "https://login.microsoftonline.com/xxx.onmicrosoft.com",
PostLogoutRedirectUri = redirectUri,
RedirectUri = redirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = context =>
{
context.HandleResponse();
context.Response.Redirect("/Error?message=" + context.Exception.Message);
return Task.FromResult(0);
}
}
});
}
AccountController.cs( Redirect to the identity data provider based on the choosing of users)
public void SignIn(bool b2c)
{
if (!Request.IsAuthenticated)
{
if (b2c)
{
// To execute a policy, you simply need to trigger an OWIN challenge.
// You can indicate which policy to use by specifying the policy id as the AuthenticationType
HttpContext.GetOwinContext().Authentication.Challenge(
new AuthenticationProperties() { RedirectUri = "/" }, Startup.SignInPolicyId);
}
else
HttpContext.GetOwinContext().Authentication.Challenge(new AuthenticationProperties { RedirectUri = "/" }, OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
}
Hope it helps.

Azure B2C Persistent Cookie

I am using Azure B2C with one identity provider configured (LinkedIn). I have a Web API (b2c bearer auth) and a Web App MVC (b2c Open Id).
I'm trying to create a persistent login - meaning the user can login via LinkedIn once every 90 days from the given device+browser.
The closest I've gotten is when I added IsPersistent = true code in the web app to enable that:
Update: Updated code based on Azure B2C GA. To achieve where I was at with Preview, I still use a custom authorize attribute, but the code was updated:
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
filterContext.HttpContext.GetOwinContext().Authentication.Challenge(
new AuthenticationProperties()
{
IsPersistent = true
});
base.HandleUnauthorizedRequest(filterContext);
}
However, this is only valid for about 1 hour. Perhaps it is following the Access & ID policy? With no bounds on the refresh token - I am not sure why only 1 hour for "IsPersistent".
Token Session Config in Azure
So that leads to these questions:
Is a Persistent session (60-90 days) something I can achieve with Azure B2C (OpenId Connect)?
If so, any pointers on what I am missing? Do I need to do some custom cookie validation? Something with refresh tokens (I use them for the web api, but nothing custom in the web app).
Any thoughts or input would be great!
I have been able to achieve a persistent session with B2C after doing the following:
Custom Authorization Attribute
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
filterContext.HttpContext.GetOwinContext()
.Authentication.Challenge(
new AuthenticationProperties() { IsPersistent = true }
);
base.HandleUnauthorizedRequest(filterContext);
}
Use Microsoft.Experimental.IdentityModel.Clients.ActiveDirectory instead of BootstrapContext (basically went with the pre-GA code sample (view change history) -> https://github.com/AzureADQuickStarts/B2C-WebApp-WebAPI-OpenIDConnect-DotNet). The ADAL library handles the getting a proper token transparent to my code.
Implemented custom TokenCache (based the EFADAL example here: https://github.com/Azure-Samples/active-directory-dotnet-webapp-webapi-multitenant-openidconnect/blob/master/TodoListWebApp/DAL/EFADALTokenCache.cs)
Changed Startup.Auth.cs:
return new OpenIdConnectAuthenticationOptions
{
MetadataAddress = String.Format(aadInstance, tenant, policy),
AuthenticationType = policy,
UseTokenLifetime = false,
ClientId = clientId,
RedirectUri = redirectUri,
PostLogoutRedirectUri = redirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = OnAuthenticationFailed,
AuthorizationCodeReceived = OnAuthorizationCodeReceived,
},
Scope = "openid offline_access",
ResponseType = "code id_token",
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
SaveSigninToken = true,
},
}

Resources