Javascript 使用Identity Server 4和ASP.NET标识添加外部登录

Javascript 使用Identity Server 4和ASP.NET标识添加外部登录,javascript,c#,angular,asp.net-identity,identityserver4,Javascript,C#,Angular,Asp.net Identity,Identityserver4,在使用Identity Server 4和ASP.NET Identity添加身份验证功能后,我计划添加Google Provider,以便用户也可以使用他们的Google+帐户登录。我使用Angular作为前端,使用ASP.NET Web Api(核心)作为后端 // Login client public login(email: string, password: string): Observable<any> { let body: any = this.encod

在使用Identity Server 4和ASP.NET Identity添加身份验证功能后,我计划添加Google Provider,以便用户也可以使用他们的Google+帐户登录。我使用Angular作为前端,使用ASP.NET Web Api(核心)作为后端

// Login client
public login(email: string, password: string): Observable<any> {
    let body: any = this.encodeParams({ /* cliend_id, grant_type, username, password, scope */ });

    return this.http.post("http://localhost:64023/connect/token", body, this.options)
        .map((res: Response) => {
            const body: any = res.json();
                if (typeof body.access_token !== "undefined") {
                    // Set localStorage with id_token,..
                }
        }).catch((error: any) => { /**/ );
}

// Register Web API
[HttpPost("Create")]
[AllowAnonymous]
public async Task<IActionResult> Create([FromBody]CreateUserViewModel model)
{
    var user = new ApplicationUser
    {
        FirstName = model.FirstName,
        LastName = model.LastName,
        AccessFailedCount = 0,
        Email = model.Email,
        EmailConfirmed = false,
        LockoutEnabled = true,
        NormalizedEmail = model.Email.ToUpper(),
        NormalizedUserName = model.Email.ToUpper(),
        TwoFactorEnabled = false,
        UserName = model.Email
    };

    var result = await _userManager.CreateAsync(user, model.Password);

    if (result.Succeeded)
    {
        await addToRole(model.Email, "user");
        await addClaims(model.Email);
    }

    return new JsonResult(result);
}

// Identity Server Startup 
app.UseGoogleAuthentication(new GoogleOptions
{
    AuthenticationScheme = "Google",
    DisplayName = "Google",
    SignInScheme = "Identity.External",
    // ClientId, ClientSecret,..
});

我发现了一些解决方案,但仅适用于MVC应用程序,而不适用于使用客户端框架的SPA。要使外部登录正常工作,我需要采取哪些步骤?当用户首次使用外部提供商登录时,是否需要在AspNetUsers表中创建新记录?

登录应由identityServer4处理。无论是本地登录还是第三方登录,它都应该向web应用程序返回类似的令牌

如果您需要Web API后端中的用户数据,则应将其作为声明传递


IdentityServer 4快速启动可能对您的代码有帮助,其中有一个示例,您可以将其添加到IdentityServer中,以及如何从中执行登录流。

您可以检查此存储库,您可以忽略ids4服务器项目,并检查您应该使用openid客户端来执行此操作的angular客户端,然后,客户端被重定向到ids4项目登录页面,您可以在该页面上登录,并返回一个令牌,您可以保存该令牌以便以后使用


我想在这里发表一些想法,因为从完美用户体验的角度来看,我经常在这方面遇到困难。很久以前,我就能够通过使用手动方法来实现这一点。我不确定这是否是Identity Server,但是,它可以作为ASP.NET Identity系统的一部分与Identity Server一起实现

注意:理想的情况是,通过将用户定向到身份提供商的登录页面(在本例中为Identity Server),允许用户在Google/FB上登录/注册。他们登录到谷歌或FB,然后被Identity Server重定向回应用程序。这是隐式或PKCE流。PKCE有额外的步骤,但索赔处理等全部由IdSrv代码完成。这是你看到的最广泛的记录

最常请求的场景

我经常有客户要求我们通过相应的Javascript库使用Javascript Google和FB登录。这就是你所说的情景。本例中的流程如下所示:

  • 用户到达您的angular或react应用程序页面

  • 他们使用Google/FB登录-这将为我们提供“外部访问令牌”。i、 e.谷歌和FB的代币

  • 然后,您可以获取此令牌信息,并将其与任何其他信息(如客户端id、租户id等)一起传递给您可以创建的新API方法

  • API可以有一个名为“POST API/local token”的方法,该方法将在内部验证外部访问令牌的真实性。如果信息已签出,则为用户生成一个新的特定于应用程序的承载访问令牌,并将其作为响应传递回Javascript应用程序。然后,JS应用程序可以将外部访问令牌(用于FB/Google API请求)和应用程序的访问令牌(用于您的应用程序API请求)存储在cookie/本地存储中,然后从那里继续

    我很容易做到这一点

    如果您想生成访问令牌,它相当于str8 forward,下面是我代码中的一个示例。这将生成与Identity Server生成的响应类似的响应:

      public static async Task<JObject> GenerateLocalAccessTokenResponse(string userName, string role, string userId, string clientId, string provider)
        {
    
            var tokenExpiration = TimeSpan.FromDays(1);
    
            var identity = new ClaimsIdentity(OAuthDefaults.AuthenticationType);
    
            identity.AddClaim(new Claim(ClaimTypes.Name, userName));
            identity.AddClaim(new Claim("ClientId", clientId));
            identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, userId));
            identity.AddClaim(new Claim(ClaimTypes.Role, role));
    
    
            var data = new Dictionary<string, string>
            {
                {"userName", userName},
                {"client_id", clientId},
                {"role", role},
                {"provider", provider},
                {"userId", userId}
            };
    
            var props = new AuthenticationProperties(data);
    
            var ticket = new AuthenticationTicket(identity, props);
    
            var accessToken = Startup.OAuthOptions.AccessTokenFormat.Protect(ticket);
    
            var tokenResponse = new JObject(
                new JProperty("userName", userName),
                new JProperty("client_id", clientId),
                new JProperty("role", role),
                new JProperty("provider", provider),
                new JProperty("userId", userId),
                new JProperty("access_token", accessToken),
                new JProperty("token_type", "bearer"),
                new JProperty("expires_in", tokenExpiration.TotalSeconds.ToString()),
                new JProperty(".issued", ticket.Properties.IssuedUtc.ToString()),
                new JProperty(".expires", ticket.Properties.ExpiresUtc.ToString())
                );
    
            return tokenResponse;
        }
    
    公共静态异步任务GenerateLocalAccessTokenResponse(字符串用户名、字符串角色、字符串用户ID、字符串客户端ID、字符串提供程序)
    {
    var tokenExpiration=TimeSpan.FromDays(1);
    var identity=newclaimsidentity(OAuthDefaults.AuthenticationType);
    identity.AddClaim(新声明(ClaimTypes.Name,userName));
    identity.AddClaim(新的索赔(“ClientId”,ClientId));
    identity.AddClaim(新声明(ClaimTypes.NameIdentifier,userId));
    identity.AddClaim(新声明(ClaimTypes.Role,Role));
    var数据=新字典
    {
    {“用户名”,用户名},
    {“client_id”,clientId},
    {“角色”,角色},
    {“提供者”,提供者},
    {“userId”,userId}
    };
    var props=新的AuthenticationProperties(数据);
    var票证=新的身份验证票证(身份、道具);
    var accessToken=Startup.OAuthOptions.AccessTokenFormat.Protect(票证);
    var tokenResponse=newjobject(
    新的JProperty(“用户名”,用户名),
    新的JProperty(“客户id”,客户id),
    新JProperty(“角色”,角色),
    新的JProperty(“提供方”,提供方),
    新的JProperty(“userId”,userId),
    新的JProperty(“访问令牌”,accessToken),
    新产权(“令牌类型”、“持有人”),
    新的JProperty(“expires_in”,tokenExpirement.TotalSeconds.ToString()),
    新JProperty(“.issued”,ticket.Properties.IssuedUtc.ToString()),
    新的JProperty(“.expires”,ticket.Properties.ExpiresUtc.ToString())
    );
    返回令牌响应;
    }
    
    登录不应该由提供商处理吗?选择google帐户后,我从提供者而不是Identity Server生成的响应中检索令牌。我已经按照您发送给我的指南进行了操作,但遗憾的是,它没有提供有关将外部提供商添加到结合ASP.NET Identity的SPA的任何信息。您正在从Identity Server和Google接收令牌,具体取决于登录类型,它们可能不包含您所需的声明。如果您的identity Server允许Google登录,那么您的应用程序将只获得1种类型的令牌,始终来自identity Server。Identity Server仍会将您传递到Google进行身份验证,但它会在返回到您的应用程序之前返回Identity Server。感谢您共享该存储库。如果我不是错的话
      public static async Task<JObject> GenerateLocalAccessTokenResponse(string userName, string role, string userId, string clientId, string provider)
        {
    
            var tokenExpiration = TimeSpan.FromDays(1);
    
            var identity = new ClaimsIdentity(OAuthDefaults.AuthenticationType);
    
            identity.AddClaim(new Claim(ClaimTypes.Name, userName));
            identity.AddClaim(new Claim("ClientId", clientId));
            identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, userId));
            identity.AddClaim(new Claim(ClaimTypes.Role, role));
    
    
            var data = new Dictionary<string, string>
            {
                {"userName", userName},
                {"client_id", clientId},
                {"role", role},
                {"provider", provider},
                {"userId", userId}
            };
    
            var props = new AuthenticationProperties(data);
    
            var ticket = new AuthenticationTicket(identity, props);
    
            var accessToken = Startup.OAuthOptions.AccessTokenFormat.Protect(ticket);
    
            var tokenResponse = new JObject(
                new JProperty("userName", userName),
                new JProperty("client_id", clientId),
                new JProperty("role", role),
                new JProperty("provider", provider),
                new JProperty("userId", userId),
                new JProperty("access_token", accessToken),
                new JProperty("token_type", "bearer"),
                new JProperty("expires_in", tokenExpiration.TotalSeconds.ToString()),
                new JProperty(".issued", ticket.Properties.IssuedUtc.ToString()),
                new JProperty(".expires", ticket.Properties.ExpiresUtc.ToString())
                );
    
            return tokenResponse;
        }