[ ] OpenId Connect ASP.NET Core IdentityServer4 oidc-client |
, OpenId Connect ASP.NET Core. , , , . , , , OpenId Connect Implicit Flow ASP.NET Core, , .
, , . . , .
OpenId Connect, .
OpenId Connect ( OpenId) , OAuth2.0. , OAuth2 , . OpenID Connect , claims. OpenId Connect UserInfo endpoint, . JSON Web Token (JWT), .
, Connect2id, Auth0 Stormpath. , - , .
OpenId Connect Implicit Flow, JavaScript-, , SPA. , , . , OpenId Connect, , .
3 :
: Client IdentityServer access_token (JWT), Bearer- - Api.
OpenId Connect . Flow.
Implicit Flow, , :
, , , Quickstart.
Api IdentityServer dotnet run
IdentityServer , .
, , .
. , Visual Studio 2017 (15.3).
solution OpenIdConnectSample.
, Implicit Flow.
solution , ASP.NET Core 1.1.
NuGet-
Install-Package Microsoft.AspNetCore.Mvc -Version 1.1.3
Install-Package Microsoft.AspNetCore.StaticFiles -Version 1.1.2
Install-Package IdentityServer4 -Version 1.5.2
, .. Install-Package
. IdentityServer Asp.NET Core 2.0 dev-, , Quickstart UI. .NET Core 1.1 2.0 .
Main
Program.cs ,
public static void Main(string[] args)
{
Console.Title = "IdentityServer";
// https://docs.microsoft.com/en-us/aspnet/core/fundamentals/servers/kestrel?tabs=aspnetcore2x
var host = new WebHostBuilder()
.UseKestrel()
// , Kestrel
.UseUrls("http://localhost:5000")
// UI -
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup()
.Build();
host.Run();
}
Startup.cs
using System.Security.Claims;
using IdentityServer4;
using IdentityServer4.Configuration;
using IdentityServer4.Models;
using IdentityServer4.Test;
ConfigureServices
. , .
public static IEnumerable GetIdentityResources()
{
// , scopes IdentityServer
return new List
{
// "sub" claim
new IdentityResources.OpenId(),
// claims profile scope
// http://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims
new IdentityResources.Profile(),
};
}
claim sub
, OpenId Connect, claim scope profile
, OpenId Connect , , .
, API
public static IEnumerable GetApiResources()
{
// claims scopes access_token
return new List
{
// scope "api1" IdentityServer
new ApiResource("api1", "API 1",
// claims scope api1
new[] {"name", "role" })
};
}
,
public static IEnumerable GetClients()
{
return new List
{
new Client
{
// , client_id
ClientId = "js",
ClientName = "JavaScript Client",
AllowedGrantTypes = GrantTypes.Implicit,
AllowAccessTokensViaBrowser = true,
// ,
// false UserInfo endpoint
AlwaysIncludeUserClaimsInIdToken = true,
//
// User Agent,
RedirectUris = {
//
"http://localhost:5003/callback.html",
// access_token iframe
"http://localhost:5003/callback-silent.html"
},
PostLogoutRedirectUris = { "http://localhost:5003/index.html" },
// , CORS-
AllowedCorsOrigins = { "http://localhost:5003" },
// scopes,
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"api1"
},
AccessTokenLifetime = 300, // ,
IdentityTokenLifetime = 3600, // ,
// refresh- scope offline_access
AllowOfflineAccess = false,
}
};
}
, , bob
public static List GetUsers()
{
return new List
{
new TestUser
{
SubjectId = "1",
Username = "alice",
Password = "password",
Claims = new List
{
new Claim("name", "Alice"),
new Claim("website", "https://alice.com"),
new Claim("role", "user"),
}
},
new TestUser
{
SubjectId = "2",
Username = "bob",
Password = "password",
Claims = new List
{
new Claim("name", "Bob"),
new Claim("website", "https://bob.com"),
new Claim("role", "admin"),
}
}
};
}
ConfigureServices
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddIdentityServer(options =>
{
// http://docs.identityserver.io/en/release/reference/options.html#refoptions
options.Endpoints = new EndpointsOptions
{
// Implicit Flow
EnableAuthorizeEndpoint = true,
//
EnableCheckSessionEndpoint = true,
//
EnableEndSessionEndpoint = true,
// claims
// http://openid.net/specs/openid-connect-core-1_0.html#UserInfo
EnableUserInfoEndpoint = true,
// OpenId Connect
EnableDiscoveryEndpoint = true,
// ,
EnableIntrospectionEndpoint = false,
// .. Implicit Flow access_token authorization_endpoint
EnableTokenEndpoint = false,
// refresh reference tokens
// http://docs.identityserver.io/en/release/topics/reference_tokens.html
EnableTokenRevocationEndpoint = false
};
// IdentitySever cookie
options.Authentication = new IdentityServer4.Configuration.AuthenticationOptions
{
CookieLifetime = TimeSpan.FromDays(1)
};
})
// x509-, IdentityServer RS256 JWT
.AddDeveloperSigningCredential()
// id_token
.AddInMemoryIdentityResources(GetIdentityResources())
// access_token
.AddInMemoryApiResources(GetApiResources())
//
.AddInMemoryClients(GetClients())
//
.AddTestUsers(GetUsers());
}
IdentityServer, , , scope
OpenId Connect OAuth2.0, -, .
. AddIdentityServer
IdentityServer ASP.NET Core, , middleware Configure
.
AddDeveloperSigningCredential
JWT-, id_token, access_token . , , . AddInMemoryIdentityResources
. , , . Configure
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(LogLevel.Debug);
app.UseDeveloperExceptionPage();
// middleware IdentityServer
app.UseIdentityServer();
// 2 ,
app.UseStaticFiles();
app.UseMvcWithDefaultRoute();
}
Starter UI IdentityServer, , , wwwroot wwwroot.
, .
API .
solution Api, ASP.NET Core 1.1. .. - , -, JSON, MvcCore middleware Mvc.
, Package Manager Console
Install-Package Microsoft.AspNetCore.Mvc.Core -Version 1.1.3
Install-Package Microsoft.AspNetCore.Mvc.Formatters.Json -Version 1.1.3
Install-Package Microsoft.AspNetCore.Cors -Version 1.1.2
Install-Package IdentityServer4.AccessTokenValidation -Version 1.2.1
, Kestrel Program.cs
public static void Main(string[] args)
{
Console.Title = "API";
var host = new WebHostBuilder()
.UseKestrel()
.UseUrls("http://localhost:5001")
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup()
.Build();
host.Run();
}
Startup.cs .
ConfigureServices
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options=>
{
// CORS, API
options.AddPolicy("default", policy =>
{
policy.WithOrigins("http://localhost:5003")
.AllowAnyHeader()
.AllowAnyMethod();
});
});
// MVC Core Razor, DataAnnotations , Asp.NET 4.5 WebApi
services.AddMvcCore()
// , Authorize
.AddAuthorization(options =>
// Roles magic strings,
options.AddPolicy("AdminsOnly", policyUser =>
{
policyUser.RequireClaim("role", "admin");
})
)
// AddMVC, AddMvcCore, JSON
.AddJsonFormatters();
}
Configure
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(LogLevel.Debug);
// middleware CORS
app.UseCors("default");
// middleware OpenId Connect JWT-
app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
{
// IdentityServer
Authority = "http://localhost:5000",
// , HTTPS IdentityServer, true
// https://docs.microsoft.com/en-us/aspnet/core/api/microsoft.aspnetcore.builder.openidconnectoptions
RequireHttpsMetadata = false,
// aud access_token JWT
ApiName = "api1",
// , api scopes scope
// AllowedScopes = { "api1.read", "api1.write" }
// JWT- claims HttpContext.User Authorize ,
AutomaticAuthenticate = true,
// middleware authentication challenge
AutomaticChallenge = true,
// [Authorize], IdentityServerAuthenticationOptions -
RoleClaimType = "role",
});
app.UseMvc();
}
, Claims , , , middleware IdentityServer access_token
.
IdentityController
.
C .
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
namespace Api.Controllers
{
[Authorize]
public class IdentityController : ControllerBase
{
[HttpGet]
[Route("identity")]
public IActionResult Get()
{
return new JsonResult(from c in User.Claims select new { c.Type, c.Value });
}
[HttpGet]
[Route("superpowers")]
[Authorize(Policy = "AdminsOnly")]
public IActionResult Superpowers()
{
return new JsonResult("Superpowers!");
}
}
}
, .
. - Kestrel, .
, 2 , Client.
.
Install-Package Microsoft.AspNetCore.StaticFiles -Version 1.1.2
Program.cs
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseKestrel()
.UseUrls("http://localhost:5003")
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup()
.Build();
host.Run();
}
Startup
.
public void ConfigureServices(IServiceCollection services)
{
}
public void Configure(IApplicationBuilder app)
{
app.UseDefaultFiles();
app.UseStaticFiles();
}
JavaScript, , Api.
wwwroot .
index.html
HTML- JavaScript- app.js
oidc-client.js
. oidc-client.js
, OpenId Connectapp.js
oidc-client callback.html
, , , . callback-silent.html
, callback.html
, , "" iframe. refresh_token
. index.html
HTML- wwwroot .
app.js
JavaScript- wwwroot .
///
IntelliSense.
app.js
Oidc.Log.logger = console;
Oidc.Log.level = 4;
, , oidc-client. . , , , .
.
, , , . UserManager
oidc-client, . .
var config = {
authority: "http://localhost:5000", // IdentityServer
client_id: "js", // IdentityServer
// ,
// - OpenId Connect
redirect_uri: "http://localhost:5003/callback.html",
// Response Type , Authorization Endpoint
// , Implicit Flow
// http://openid.net/specs/openid-connect-core-1_0.html#Authentication
response_type: "id_token token",
// subject id , id_token, access_token api1 (. c IdentityServer)
scope: "openid profile api1",
// ,
post_logout_redirect_uri: "http://localhost:5003/index.html",
// IdentityServer, true
monitorSession: true,
// , , 2000
checkSessionInterval: 30000,
// access_token https://tools.ietf.org/html/rfc7009
revokeAccessTokenOnSignout: true,
// , , 300
// https://github.com/IdentityModel/oidc-client-js/blob/1.3.0/src/JoseUtil.js#L95
clockSkew: 300,
// UserInfo endpoint ,
loadUserInfo: true,
};
var mgr = new Oidc.UserManager(config);
.
function login() {
//
mgr.signinRedirect();
}
function displayUser() {
mgr.getUser().then(function (user) {
if (user) {
log("User logged in", user.profile);
}
else {
log("User not logged in");
}
});
}
function api() {
// claims
requestUrl(mgr, "http://localhost:5001/identity");
}
function getSuperpowers() {
// endpoint
requestUrl(mgr, "http://localhost:5001/superpowers");
}
function logout() {
//
mgr.signoutRedirect();
}
document.getElementById("login").addEventListener("click", login, false);
document.getElementById("api").addEventListener("click", api, false);
document.getElementById("getSuperpowers").addEventListener("click", getSuperpowers, false);
document.getElementById("logout").addEventListener("click", logout, false);
document.getElementById("getUser").addEventListener("click", displayUser, false);
//
displayUser();
function requestUrl(mgr, url) {
mgr.getUser().then(function (user) {
var xhr = new XMLHttpRequest();
xhr.open("GET", url);
xhr.onload = function () {
log(xhr.status, 200 == xhr.status ? JSON.parse(xhr.responseText) : "An error has occured.");
}
// Authorization access_token Bearer - .
xhr.setRequestHeader("Authorization", "Bearer " + user.access_token);
xhr.send();
});
}
function log() {
document.getElementById('results').innerText = '';
Array.prototype.forEach.call(arguments, function (msg) {
if (msg instanceof Error) {
msg = "Error: " + msg.message;
}
else if (typeof msg !== 'string') {
msg = JSON.stringify(msg, null, 2);
}
document.getElementById('results').innerHTML += msg + '\r\n';
});
}
, , , . wwwroot
.
callback.html
callback-silent.html
!
: , , dotnet run
. IdentityServer .
IdentityServer Api, Client.
http://localhost:5003/index.html
Client.
clear()
.
, .
, Chrome 60 .
Network Preserve log .
CTRL+F5.
, .
1. , .
2. .
Login.
GET-
http://localhost:5000/.well-known/openid-configuration
oidc-client OpenId Connect ( ), authorization_endpoint
http://localhost:5000/connect/authorize
, WebStorage. oidc-client , , sessionStorage
.
client_id | js |
redirect_uri | http://localhost:5003/callback.html |
response_type | id_token token |
scope | openid profile api1 |
state | |
nonce |
, redirect_uri , client_id
js IdentityServer.
http://localhost:5000/account/login
.
3. .
4. .
5. id token' , , access token'.
bob password , .
authorization_endpoint
, OpenId Connect relying party ( js-) scopes.
, . , authorization_endpoint
, authorization_endpoint
cookie.
, redirect_uri
.
Implicit Flow #
. , JavaScript, -.
id_token | |
access_token | API |
token_type | access_token , Bearer |
expires_in | access_token |
scope | scopes |
6. id token Subject Identifier .
oidc-client state, nonce id_token
. , (, sub
claim id_token
). id_token
oidc-client .
id_token
( Network ), , payload -
{
"nbf": 1505143180,
"exp": 1505146780,
"iss": "http://localhost:5000",
"aud": "js",
"nonce": "2bd3ed0b260e407e8edd0d03a32f150c",
"iat": 1505143180,
"at_hash": "UAeZEg7xr23ToH2R2aUGOA",
"sid": "053b5d83fd8d3ce3b13d3b175d5317f2",
"sub": "2",
"auth_time": 1505143180,
"idp": "local",
"name": "Bob",
"website": "https://bob.com",
"amr": [
"pwd"
]
}
access_token
payload , , .
{
"nbf": 1505143180,
"exp": 1505143480,
"iss": "http://localhost:5000",
"aud": [
"http://localhost:5000/resources",
"api1"
],
"client_id": "js",
"sub": "2",
"auth_time": 1505143180,
"idp": "local",
"name": "Bob",
"role": "admin",
"scope": [
"openid",
"profile",
"api1"
],
"amr": [
"pwd"
]
}
, loadUserInfo
, UserInfo Endpoint. UserInfo Endpoint claims Authorization Bearer- access_token
, claims JavaScript- .
loadUserInfo
access_token
, HTTP-, .
"Call API".
ajax- http://localhost:5001/identity
.
, OPTIONS- CORS .. , "" (Authorization
, ).
, , GET-. , Authorization Bearer < access_token>.
IdentityServer middleware . IdentityServer middleware Asp.Net Core JwtBearerMiddleware.
, 200.
GET- end_session_endpoint
id_token_hint | id_token |
post_logout_redirect_uri | URI, , |
, .
, . , - , - .
alice Get Superpowers!, bob .
Logout ,
Username: alice
Password: password
http://localhost:5000/consent
No, Do Not Allow.
http://localhost:5003/callback.html
.
, URL #error=access_denied
, signinRedirectCallback
, rejected.
callback.html
catch-, .
id_token
URL , claims, scope profile.
API .
claim api1
"scope": [
"openid",
"profile"
],
Api 401 (Unathorized).
access_token
, Call API.
API ! , IdentityServer middleware Asp.Net Core, ClockSkew. , , , , . ClockSkew 5 .
5 , API 401 (Unathorized).
401, access_token
.
app.js
config
,
var config = {
// ...
// true, access_token , false
automaticSilentRenew: true,
// "" iframe
silent_redirect_uri: 'http://localhost:5003/callback-silent.html',
// oidc-client access_token
accessTokenExpiringNotificationTime: 60,
// ...
}
access_token
. Call API , .
access_token
API , , , id_token
. js-. .
, :
id_token
access_token
.