2

I have an Asp.net 4.5 MVC5 app using SimpleMembership for Authentication and Authorization in App. Now I need to integrate to use external IdentityServer4 for Authentication. I tried to add OWIN Startup file into the project and turn off standard IIS authentication in web.config. Now I could be redirected to IDServer for Login and Consent.

Issue occured when it get to the view:

Error message:

No user found was found that has the name "". Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

Exception Details: System.InvalidOperationException: No user found was found that has the name "".

Source Error:

Line 15: @if (Model != null) Line 16: { Line 17:
foreach (var claim in Model) Line 18: { Line 19:

@claim.Type

Source File: e:\GitHub\CRURepoPostBR20190607_IS4\CRU_Build\Suite\Views\Home\IS4Auth.cshtml Line: 17

Stack Trace: [InvalidOperationException: No user found was found that has the name "".]
WebMatrix.WebData.SimpleRoleProvider.GetRolesForUser(String username) +498 System.Web.Security.RolePrincipal.GetRoles() +215 System.Web.Security.d__4.MoveNext() +58
System.Security.Claims.d__51.MoveNext() +253
System.Security.Claims.d__37.MoveNext() +209

My IdentityServer is the clone of IdentityServer4 quickstart sample. My MVC5 App is old and use SimpleMembership for Authentication and role Authorization. I manually added the OWIN Startup.cs see below code.

I tried to use IdentityServer https://demo.identityserver.io as IDServer and got same issue. This proved my IDServer has no problem.

I tried to created a simple separate Asp.net 4.5 MVC5 project without SimpleMembership, it doesn't get the error.

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        app.UseCookieAuthentication(new CookieAuthenticationOptions
        {
            AuthenticationType = "Cookies"
        });
        app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
        {
            AuthenticationType = "oidc",
            SignInAsAuthenticationType = "Cookies",
            ClientSecret = "secret",

            //Authority = "http://localhost:5000", //ID Server
            Authority = "https://demo.identityserver.io", //ID Server
            RequireHttpsMetadata = false,
            RedirectUri = "http://localhost:58018/signin-oidc",
            //RedirectUri = "http://localhost:58018/",
            //ClientId = "myOldMvc",
            ClientId = "server.hybrid",

            ResponseType = "id_token code",
            Scope = "openid profile",
        });
        AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.NameIdentifier;
    }
}

Here is the view to display the claim:

@model IEnumerable<System.Security.Claims.Claim>

@{
    ViewBag.Title = "IS4Auth";
}

<h2>IS4Auth</h2>

<dl>

    @if (Model != null)
    {
        foreach (var claim in Model)
        {
            <dt>@claim.Type</dt>
            <dd>@claim.Value</dd>
        }
    }
</dl>

here is my config related part for system.web and system.webServer

<system.web>
    <httpModules>
      <add name="ar.sessionscope" type="Castle.ActiveRecord.Framework.SessionScopeWebModule, Castle.ActiveRecord.web" />
      <add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah" />
      <add name="ErrorMail" type="Elmah.ErrorMailModule, Elmah" />
      <add name="ErrorFilter" type="Elmah.ErrorFilterModule, Elmah" />
    </httpModules>
    <compilation debug="true" targetFramework="4.5" />
    <httpRuntime targetFramework="4.5" maxRequestLength="1048576" />
    <!--Change for IS4Authentication-->
    <authentication mode="None" />
    <!--<authentication mode="Forms">
      <forms loginUrl="~/Account/Login" timeout="2880" />
    </authentication>-->
    <!--<roleManager enabled="true" defaultProvider="simple">
      <providers>
        <clear />
        <add name="simple" type="WebMatrix.WebData.SimpleRoleProvider,WebMatrix.WebData" />
      </providers>
    </roleManager>
    <membership defaultProvider="simple">
      <providers>
        <clear />
        <add name="simple" type="WebMatrix.WebData.SimpleMembershipProvider,WebMatrix.WebData" enablePasswordReset="true" requiresQuestionAndAnswer="false" />
      </providers>
    </membership>-->
    <pages>
      <namespaces>
        <add namespace="System.Web.Helpers" />
        <add namespace="System.Web.Mvc" />
        <add namespace="System.Web.Mvc.Ajax" />
        <add namespace="System.Web.Mvc.Html" />
        <add namespace="System.Web.Optimization" />
        <add namespace="System.Web.Routing" />
        <add namespace="System.Web.WebPages" />
      </namespaces>
    </pages>
    <httpHandlers>
      <add verb="*" path="routes.axd" type="AttributeRouting.Web.Logging.LogRoutesHandler, AttributeRouting.Web" />
      <add verb="POST,GET,HEAD" path="elmah" type="Elmah.ErrorLogPageFactory, Elmah" />
    </httpHandlers>
  </system.web>
  <system.webServer>
    <validation validateIntegratedModeConfiguration="false" />
    <handlers>
      <remove name="ExtensionlessUrlHandler-ISAPI-4.0_32bit" />
      <remove name="ExtensionlessUrlHandler-ISAPI-4.0_64bit" />
      <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
      <add name="ExtensionlessUrlHandler-ISAPI-4.0_32bit" path="*." verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" responseBufferLimit="0" />
      <add name="ExtensionlessUrlHandler-ISAPI-4.0_64bit" path="*." verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" responseBufferLimit="0" />
      <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
      <add name="Elmah" verb="POST,GET,HEAD" path="elmah" type="Elmah.ErrorLogPageFactory, Elmah" />
    </handlers>
    <modules>
      <!--Change for IS4Authentication-->
      <remove name="FormsAuthentication" />
      <add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah" preCondition="managedHandler" />
      <add name="ErrorMail" type="Elmah.ErrorMailModule, Elmah" preCondition="managedHandler" />
      <add name="ErrorFilter" type="Elmah.ErrorFilterModule, Elmah" preCondition="managedHandler" />
    </modules>
    <httpProtocol>
      <customHeaders>
        <clear />
        <add name="X-UA-Compatible" value="IE=Edge" />
      </customHeaders>
    </httpProtocol>

  </system.webServer>

Here is the Notifications code added to OpenIdConnectAuthenticationOptions

    Notifications = new OpenIdConnectAuthenticationNotifications
                {
                    AuthorizationCodeReceived = async n =>
                    {
                        // use the code to get the access and refresh token
                        var tokenClient = new TokenClient(
                            "http://localhost:5000/connect/token",
                            //"server.hybrid",
                            "myApp",
                            "secret");

                        var tokenResponse = await tokenClient.RequestAuthorizationCodeAsync(n.Code, n.RedirectUri);

                        if (tokenResponse.IsError)
                        {
                            //throw new Exception(tokenResponse.Error);
                            throw new AuthenticationException(tokenResponse.Error);
                        }

                        // use the access token to retrieve claims from userinfo
                        var userInfoClient = new UserInfoClient("http://localhost:5000/connect/userinfo");
                        var userInfoResponse = await userInfoClient.GetAsync(tokenResponse.AccessToken);

                        // create new identity
                        var id = new ClaimsIdentity(n.AuthenticationTicket.Identity.AuthenticationType);
                        id.AddClaims(userInfoResponse.Claims);
                        var Name = userInfoResponse.Claims.FirstOrDefault(c => c.Type.Equals("Name", StringComparison.CurrentCultureIgnoreCase)).Value;
                        id.AddClaim(new Claim("access_token", tokenResponse.AccessToken));
                        id.AddClaim(new Claim("expires_at", DateTime.Now.AddSeconds(tokenResponse.ExpiresIn).ToLocalTime().ToString()));
                        id.AddClaim(new Claim("refresh_token", tokenResponse.RefreshToken));
                        id.AddClaim(new Claim("id_token", n.ProtocolMessage.IdToken));
                        id.AddClaim(new Claim("sid", n.AuthenticationTicket.Identity.FindFirst("sid").Value));

                        n.AuthenticationTicket = new AuthenticationTicket(
                            new ClaimsIdentity(id.Claims, n.AuthenticationTicket.Identity.AuthenticationType, JwtClaimTypes.Name, JwtClaimTypes.Role),
                            n.AuthenticationTicket.Properties
                        );

                        List<Claim> _claims = new List<Claim>();
                        _claims.AddRange(new List<Claim>
                        {
                            new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier", Name),
                            new Claim("http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider",Name)
                        });
                    },

                    RedirectToIdentityProvider = n =>
                    {
                        if (n.ProtocolMessage.RequestType != OpenIdConnectRequestType.Logout)
                        {
                            return Task.FromResult(0);
                        }

                        var idTokenHint = n.OwinContext.Authentication.User.FindFirst("id_token");

                        if (idTokenHint != null)
                        {
                            n.ProtocolMessage.IdTokenHint = idTokenHint.Value;
                        }

                        return Task.FromResult(0);
                    }
         }  
Tao
  • 21
  • 3
  • Thank you for the response, I already have in config, here is web and webServer part of config: Here is my related part in config – Tao Jun 27 '19 at 18:40
  • Thank you for that. I cleared the cookie and the error is the same. I add more detailed error message. – Tao Jun 27 '19 at 19:31
  • I am using WebMatrix.WebData everywhere in my App. Not using Microsoft.AspNet.WebPages.WebData. – Tao Jun 27 '19 at 20:40
  • everywhere? then that might become a problem :) haven't used it myself (jumped from original Membership directly to WIF), but as I see from descriptions -- most of the stuff there is about managing users and roles, and as you switch to oidc based on IdSrv (and MS Identity?) nothing should prevent you from getting rid of old infrastructure. Probably you can start from looking into `InitializeSimpleMembershipAttribute` for switching that off. – d_f Jun 28 '19 at 08:50
  • d_f, thank you so much. I should say WebMatrix.WebData is used in many places. I did get rid of WebMatrix.WebData and tried to build, then got 70 errors, and, you are right, all of them are used to manage User and roles. In the app, we use roles for authorization in many places, and our concern is to keep the roles in our local database, and only use IdSrv as an external authentication tool. Can we do that? or do we have to git rid of SimpleMembership totally and use IdSrv roles? – Tao Jun 28 '19 at 16:03
  • I think you can. You then will need to handle that in `.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions() {Notifications = new OpenIdConnectAuthenticationNotifications {SecurityTokenValidated = async n => {}});` directly calling webmatrix and adding the roles to your new Identity. – d_f Jun 28 '19 at 16:21
  • Thank you again. By your advice, I followed some examples to use Notification in OpenIdConnectAuthenticationOptions, and that fixed the issue: "No user found was found that has the name "" " by using the access token to retrieve claims from userinfo expecitly. I will add the code in the question post above. Now I could login in with IdSrv and redirect to my app, but most feature are filtered out because the roles. Do you have sample code to directly call webmatrix and add the roles to new Identity in OpenIdConnectAuthenticationNotifications {SecurityTokenValidated = async n => {}}); Thank you. – Tao Jul 01 '19 at 17:09
  • As I said, I never used webmatrix, so that's for you. You have to read the roles for your user (most likely using the data access layer from the Matrix directly) and add them to your new identity like you do for the others: `id.AddClaim(new Claim(JwtClaimTypes.Role, ));`. After that you can write a nice answer for followers. – d_f Jul 01 '19 at 17:29
  • Thanks a lot to d_f, I moved out role from scope and move my code to another environment, it's work very well. – Tao Jul 02 '19 at 19:41

1 Answers1

0

Thanks for d_f's hlep, my issue is resolved. Solution is to use Notifications to retrieve Claims from userInfo EndPoint explictly and add them to new Identity. See above code in Notifications = new OpenIdConnectAuthenticationNotifications{}

Tao
  • 21
  • 3