4

I have added ASP.NET Core identity and Identity Server4 in one project with one database, and I want to use my Identity Server in all other project.

IdentityServer4 Startup Class

public class Startup
{
    public IConfigurationRoot Config { get; set; }

    public Startup(IConfiguration configuration)
    {
        Config = new ConfigurationBuilder()
                     .SetBasePath(Directory.GetCurrentDirectory())
                     .AddJsonFile("appsettings.json", false)
                     .Build();

        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        IdentityModelEventSource.ShowPII = true;

        //=== Identity Config ===
        string ConnectionString = Config.GetSection("AppSettings:DefaultConnection").Value;
        var migrationAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;

        //-----------------------------------------------------------------
        services.AddDbContext<MyIdentityDbContext>(options =>
             options.UseSqlServer(ConnectionString, sql => sql.MigrationsAssembly(migrationAssembly)));

        //-----------------------------------------------------------------
        services.AddIdentity<MyIdentityUser, IdentityRole>(op =>
        {
            op.Password.RequireDigit = false;
            op.Password.RequiredLength = 6;
            op.Password.RequireUppercase = false;
            op.Password.RequireLowercase = false;
            op.Password.RequireNonAlphanumeric = false;
        })
        .AddEntityFrameworkStores<MyIdentityDbContext>()
        .AddDefaultTokenProviders();

        //=== IdentityServer4 config ===
        services.AddIdentityServer(options =>
        {
            options.Events.RaiseErrorEvents = true;
            options.Events.RaiseInformationEvents = true;
            options.Events.RaiseFailureEvents = true;
            options.Events.RaiseSuccessEvents = true;
        })
            .AddDeveloperSigningCredential()
            .AddConfigurationStore(options =>
            {
                options.ConfigureDbContext = b => b.UseSqlServer(ConnectionString, sql => sql.MigrationsAssembly(migrationAssembly));
            })
            .AddOperationalStore(options =>
            {
                options.ConfigureDbContext = b => b.UseSqlServer(ConnectionString, sql => sql.MigrationsAssembly(migrationAssembly));
            })
            .AddAspNetIdentity<MyIdentityUser>();

        services.AddMvc(options => options.EnableEndpointRouting = false);
        services.AddAuthorization();
        services.AddControllers();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        app.UseAuthentication();
        app.UseRouting();

        app.UseAuthorization();
        app.UseIdentityServer();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }
}

My config class that I have seed my identity database with that:

public class Config
{
    public static IEnumerable<IdentityResource> GetIdentityResources()
    {
        return new List<IdentityResource>
        {
            new IdentityResources.OpenId(),
            new IdentityResources.Email(),
            new IdentityResources.Profile(),
        };
    }

    public static IEnumerable<ApiResource> GetApis()
    {
        return new List<ApiResource>
        {
            new ApiResource("MyAPI", "My asp.net core web api"),
        };
    }

    public static IEnumerable<Client> GetClients()
    {
        return new List<Client>
        {
            new Client()
            {
                 ClientId = "MyAndroidApp",
                 ClientName = "My Application for Android",
                 AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
                 ClientSecrets =
                 {
                    new Secret("secret".Sha256())
                 },
                 AllowedScopes=
                 {
                     IdentityServerConstants.StandardScopes.OpenId,
                     IdentityServerConstants.StandardScopes.Profile,
                     IdentityServerConstants.StandardScopes.Email,
                     IdentityServerConstants.StandardScopes.Address,
                     "MyAPI"
                 },
            },
        };
    }
}

I have register a user with role Admin with below action method in User controller in my IdentityServer4&Identity project

[HttpPost]
public async Task<IActionResult> Post([FromBody]SignUpModel model)
{                               
    MydentityUser NewUser = new MydentityUser ()
            {
                UserName = model.UserName,
            };
    IdentityResult result = await UserManager.CreateAsync(NewUser, model.Password);

    if (result.Succeeded)
    {
        if (!RoleManager.RoleExistsAsync("Admin").Result)
        {
            IdentityResult r = RoleManager.CreateAsync(new IdentityRole("Admin")).Result;
            r = RoleManager.CreateAsync(new IdentityRole("Member")).Result;
            r = RoleManager.CreateAsync(new IdentityRole("Guest")).Result;
        }

        result = await UserManager.AddToRoleAsync(NewUser, "Admin");

        if (result.Succeeded)
        {
            List<Claim> UserClaims = new List<Claim>() {
                    new Claim("userName", NewUser.UserName),
                    new Claim(JwtClaimTypes.Role, "Admin"),
                };

            result = await UserManager.AddClaimsAsync(NewUser, UserClaims.ToArray());
            return Ok("Registered");
        }
    }            
}

Now I have another ASP.NET Web API project that I want to use this api in my android application.

My startup class

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
            .AddJwtBearer(options =>
            {
                options.Authority = "https://identity.mywebsite.ir";
                options.RequireHttpsMetadata = false;
                options.Audience = "MyAPI";                    
            });
         //I used below but not work too
        //.AddIdentityServerAuthentication(options =>
        //{
        //    options.Authority = "https://identity.mywebsite.ir";
        //    options.RequireHttpsMetadata = false;
        //    options.ApiName = "MyAPI";
        //    options.NameClaimType = ClaimTypes.Name;
        //    options.RoleClaimType = ClaimTypes.Role;                    
        //});

        services.AddOptions();
        string cs = Configuration["AppSettings:DefaultConnection"];
        services.AddDbContext<MyApiContext>(options =>
        {
            options.UseSqlServer(cs,
                sqlServerOptions =>
                {
                    sqlServerOptions.MigrationsAssembly("MyApi.Database");
                });
        });

        services.AddControllers();

        services.AddCors(options =>
        {
            options.AddPolicy("default", policy =>
            {
                policy.WithOrigins("*")
                    .AllowAnyHeader()
                    .AllowAnyMethod();
            });
        });
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        app.UseRouting();
        app.UseCors("default");
        app.UseAuthentication();
        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }
}

My Problem is how can I find userId in my Webapi when I have used user authentication with ASP.NET Core Identity in another project,

I have below action method in my two project (my webapi and identityserver & identity project). I have get token from android application from /connect/token address and I send access token with my request.

public class TestController : ControllerBase
{
    public async Task<IActionResult> Index()
    {            
        string message = "";

        if (User.Identity.IsAuthenticated)
        {
            message += "You are Registered ";
        }
        else
        {
            message += "You are not Registered ";
        }

        if (string.IsNullOrWhiteSpace(User.Identity.Name))
        {
            message += "UserId is null";
        }
        else
        {
            message += "UserId is not null";
        }

        return Ok(message);
    }
}

I get this message:

You are not registered UserId is null

How can I access to my UserId in my WebAPI? Why User.Identity.Name is null? Why is User.Identity.Claims.Count 0?

Edit

I have entered the access token in jwt.io website, this is the output

{
  "nbf": 1587133648,
  "exp": 1587137248,
  "iss": "https://identity.mywebsite.ir",
  "aud": "MyAPI",
  "client_id": "MyAndroidApp",
  "sub": "7e904278-78cc-46a8-9943-51dfeb360d8e",// I want this in my api but i get null
  "auth_time": 1587133648,
  "idp": "local",
  "scope": [
    "openid",
    "MyAPI"
  ],
  "amr": [
    "pwd"
  ]
}

MyApi Startup Class

 public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddAuthentication(options =>
            {
                options.DefaultAuthenticateScheme = IdentityServerAuthenticationDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = "oidc";
            })

        .AddIdentityServerAuthentication(options =>
        {
            options.Authority = "https://identity.mywebsite.ir";
                options.RequireHttpsMetadata = false;
            options.ApiName = "MyAPI";
            });

            services.AddOptions();
            string cs = Configuration["AppSettings:DefaultConnection"];
            services.AddDbContext<MyCommonDbContext>(options =>
            {
                options.UseSqlServer(cs,
                    sqlServerOptions =>
                    {
                        sqlServerOptions.MigrationsAssembly("MyAppProjectName");
                    });
            });
            services.AddDbContext<MyAppContext>(options =>
            {
                options.UseSqlServer(cs,
                    sqlServerOptions =>
                    {
                        sqlServerOptions.MigrationsAssembly("MyAppProjectName");
                    });
            });

            services.AddControllers();

            services.AddCors(options =>
            {
                options.AddPolicy("default", policy =>
                {
                    policy.WithOrigins("http://*.mywebsite.ir")
                        .AllowAnyHeader()
                        .AllowAnyMethod();
                });
            });
        }
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            app.UseRouting();
            app.UseCors("default");
            app.UseAuthentication();
            app.UseAuthorization();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
4
  • You need to store userId in claims while creating a token. Then use ClaimPrinciple like "User.Claims.FirstOrDefault(x=>x.type == "userId");" Commented Apr 17, 2020 at 19:28
  • You can see the UserId in token.but when I want to get that with User.Identity.Name, It is null.In Fact User.Claims.Count is 0 Commented Apr 18, 2020 at 5:05
  • Did you forget to add services.AddAuthorization to the ConfigureServices method of the API? Or did you leave it out in your example? Commented Apr 19, 2020 at 11:42
  • @Dennis1679 No,I have that in my api,I will Edit question to add my Api Startup class,And I have solved my problem I will soon answer my question,thanks Commented Apr 19, 2020 at 13:40

2 Answers 2

12

In the "MyApi" startup.cs file in ConfigureServices:

1- make sure that you do this line of code right before AddAuthentication: JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();

Because (thank!!! to microsoft -_-) by default the claim type mapping for name is :

http://schemas.microsoft.com/ws/2008/06/identity/claims/name (for name or something like this)

http://schemas.microsoft.com/ws/2008/06/identity/claims/role. ( for role )

http://schemas.microsoft.com/ws/2008/06/identity/claims/nameidentifier ( for id)

So you need clear this mapping because in your token the claim types are the jwt standard , sub == userid , and you don't embed name or roles for the moment based in your token that you shared

by the way I usually use this part of code:

services.AddAuthentication("Bearer")
                .AddJwtBearer("Bearer", options =>
                {
                    options.Authority = "";
                    options.RequireHttpsMetadata = true;
                    options.Audience = "myapi";
                    options.TokenValidationParameters = new TokenValidationParameters
                    {
                        NameClaimType = "name",
                        RoleClaimType = "role",
                    };
                });

You will need this part only:

                        options.TokenValidationParameters = new TokenValidationParameters
                    {
                        NameClaimType = "name",
                        RoleClaimType = "role",
                    };

By the way keep require https is set to true not false.

For UserId I think only clearing the default inbound type is enough.


I am not sure if you really need the second step but just double check:

2- make sure that AuthenticationScheme value is "Bearer": options.DefaultAuthenticateScheme = IdentityServerAuthenticationDefaults.AuthenticationScheme;

3- in IdentityServer4 startup

please keep the UseAuthentication after UseRouting not before (It is not related to your question but I just noticed that)

Sign up to request clarification or add additional context in comments.

2 Comments

Why UseAuthentication after UseRouting?
Because Routing Middleware has nothing to do with authentication, it's simply an early stage that should be exectued before authentication.
8

In my case the problem was for that I did not add UserClaims to ApiResources so I changed the seeding ApiResource method like below and I added the the claims,

public static IEnumerable<ApiResource> GetApis()
        {

            return new List<ApiResource>
            {
                new ApiResource("MyAPI", "My Asp.net core WebApi,the best Webapi!"){
                    UserClaims =
                    {
                        JwtClaimTypes.Name,
                        JwtClaimTypes.Subject,
                        JwtClaimTypes.Role,
                    }
                },
            };
        }

Now I will get the UserId and UserName with below code


    public static class ClaimsPrincipalExtensions
    {
        public static string GetSub(this ClaimsPrincipal principal)
        {
            return principal?.FindFirst(x => x.Type.Equals("sub"))?.Value;
        }
        public static string GetEmail(this ClaimsPrincipal principal)
        {
            return principal?.FindFirst(x => x.Type.Equals("email"))?.Value;
        }
    }

Getting UserId

string UserId=User.GetSub();

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.