2

Im trying Azure AD B2C and I've added Google and Microsoft Identity Providers through Azure Portal.

enter image description here

When i try to login with Microsoft OR Google IP, i always receive following error message in the OnAuthenticationFailed-Handler:

AADB2C99002: User does not exist. Please sign up before you can sign in.

But when i'm using the "Local Account SignIn" Provided by Azure B2C everything is working fine. Do i missing something in my configuration ?

The following code snippet shows my OWIN Configuration.

   private void ConfigureAuthentication(IAppBuilder app)
    {
        app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);

        app.UseCookieAuthentication(new CookieAuthenticationOptions());

        OpenIdConnectAuthenticationOptions options = new OpenIdConnectAuthenticationOptions
        {
            // These are standard OpenID Connect parameters, with values pulled from web.config
            ClientId = clientId,
            RedirectUri = redirectUri,
            PostLogoutRedirectUri = redirectUri,
            Notifications = new OpenIdConnectAuthenticationNotifications
            {
                AuthenticationFailed = OnAuthenticationFailed,
                RedirectToIdentityProvider = OnRedirectToIdentityProvider,
                AuthorizationCodeReceived = OnAuthorizationCodeReceived,
                SecurityTokenValidated = context => {
                    return null;
                    }
                },

            Scope = "openid offline_access",

            // The PolicyConfigurationManager takes care of getting the correct Azure AD authentication
            // endpoints from the OpenID Connect metadata endpoint.  It is included in the PolicyAuthHelpers folder.
            ConfigurationManager = new PolicyConfigurationManager(
                String.Format(CultureInfo.InvariantCulture, aadInstance, tenant, "/v2.0", OIDCMetadataSuffix),
                new string[] { SignUpPolicyId, SignInPolicyId, ProfilePolicyId }),

            // This piece is optional - it is used for displaying the user's name in the navigation bar.
            TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters
            {
                NameClaimType = "name",
            },
        };

        app.UseOpenIdConnectAuthentication(options);
    }

    // This notification can be used to manipulate the OIDC request before it is sent.  Here we use it to send the correct policy.
    private async Task OnRedirectToIdentityProvider(RedirectToIdentityProviderNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
    {
        PolicyConfigurationManager mgr = notification.Options.ConfigurationManager as PolicyConfigurationManager;
        if (notification.ProtocolMessage.RequestType == OpenIdConnectRequestType.LogoutRequest)
        {
            OpenIdConnectConfiguration config = await mgr.GetConfigurationByPolicyAsync(CancellationToken.None, notification.OwinContext.Authentication.AuthenticationResponseRevoke.Properties.Dictionary[AzureB2C.PolicyKey]);
            notification.ProtocolMessage.IssuerAddress = config.EndSessionEndpoint;
        }
        else
        {
            OpenIdConnectConfiguration config = await mgr.GetConfigurationByPolicyAsync(CancellationToken.None, notification.OwinContext.Authentication.AuthenticationResponseChallenge.Properties.Dictionary[AzureB2C.PolicyKey]);
            notification.ProtocolMessage.IssuerAddress = config.AuthorizationEndpoint;
        }
    }

    private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification notification)
    {
        // The user's objectId is extracted from the claims provided in the id_token, and used to cache tokens in ADAL
        // The authority is constructed by appending your B2C directory's name to "https://login.microsoftonline.com/"
        // The client credential is where you provide your application secret, and is used to authenticate the application to Azure AD
        string userObjectID = notification.AuthenticationTicket.Identity.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
        string authority = String.Format(CultureInfo.InvariantCulture, aadInstance, tenant, string.Empty, string.Empty);
        ClientCredential credential = new ClientCredential(clientId, clientSecret);

        // We don't care which policy is used to access the TaskService, so let's use the most recent policy
        string mostRecentPolicy = notification.AuthenticationTicket.Identity.FindFirst(AzureB2C.AcrClaimType).Value;

        // The Authentication Context is ADAL's primary class, which represents your connection to your B2C directory
        // ADAL uses an in-memory token cache by default.  In this case, we've extended the default cache to use a simple per-user session cache
        AuthenticationContext authContext = new AuthenticationContext(authority, new NaiveSessionCache(userObjectID));

        // Here you ask for a token using the web app's clientId as the scope, since the web app and service share the same clientId.
        // The token will be stored in the ADAL token cache, for use in our controllers
        AuthenticationResult result = await authContext.AcquireTokenByAuthorizationCodeAsync(notification.Code, new Uri(redirectUri), credential, new string[] { clientId }, mostRecentPolicy);
    }

    // Used for avoiding yellow-screen-of-death
    private Task OnAuthenticationFailed(AuthenticationFailedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
    {
        _log.Error("AuthenticationFailed!\r\nError={0}\r\nErrorDescription={1}\r\n{0}",
            notification.ProtocolMessage.Error,
             notification.ProtocolMessage.ErrorDescription,
            notification.Exception.ToString());

        notification.HandleResponse();
        notification.Response.Redirect("/Home/OpenIdError?message=" + notification.ProtocolMessage.ErrorDescription);
        return Task.FromResult(0);
    }
}
stef
  • 271
  • 3
  • 10

2 Answers2

6

External identities first need to 'sign up' as well before signing in. During sign up the external identity is linked to B2C.

In the sign up page you can ask additional attributes for your users, like a customer number. You need this for external identies and for the Local Account users in B2C, no difference between the two.

This is different behaviour compared to adding an identity provider without B2C, where every login just works.

Edit: Like Konstantin mentioned, the new combined sign-up or sign-in policy solves this problem: https://azure.microsoft.com/en-us/documentation/articles/active-directory-b2c-reference-policies/#create-a-sign-up-or-sign-in-policy

Erik Oppedijk
  • 3,496
  • 4
  • 31
  • 42
  • 2
    It's worthy to note that the best universal way in this case is using complex sign-in&sign-up policies (see the so-called azure AD configuration blade). If you provide just sign-in policy, the auto-sign-up sequence will be forbidden and provide nasty error pages. – Konstantin Jul 11 '16 at 18:54
0

I was running into the same issue, but was able to circumvent the user "Sign-Up" after user insertion. The issue turned out to be, that to have proper federation occur, the proper values need to be in place.

 "identities": [     
    {
      "signInType": "federated",
      "issuer": "https://login.microsoftonline.com/XXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXX/v2.0",
      "issuerAssignedId": "YYYYYYYYY-YYYY-YYYY-YYYY-YYYYYYYYYYYY"

What was happening was that I was using "issuer": "myDomain.com" which was not resolving correctly to do a login; to which the user then had to "SignUp" via the federated IP.

By changing that from DNS readable name, to the MS login with my AD directories ID (the number provided when switching domain in Azure, XXXX-XXX ...) and the proper issuerAssignedId, from the originating AD issuer, it worked and the user was added.

ΩmegaMan
  • 29,542
  • 12
  • 100
  • 122