Swashbuckle.AspNetCore. IdentityServer cookie under identity server host - identityserver4

I can`t login to my API via Swagger UI. I using Swashbuckle.AspNetCore 5.2.1.
For some reason identity server cookie stores cookie in API host under identity server host (not API host). Please advice:
This is Client configuration in IdentityServer:
new Client
{
ClientId = Applications.Swagger.ToString(),
ClientName = "Swagger",
AllowedGrantTypes = GrantTypes.Code,
RedirectUris = { urlsSettings.Api + "/swagger/oauth2-redirect.html" },
PostLogoutRedirectUris = { urlsSettings.Api + "/swagger" },
AllowedCorsOrigins = { urlsSettings.Api },
AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
Applications.API.ToString()
},
ClientSecrets = { new Secret("test-secret".Sha256()) },
AllowAccessTokensViaBrowser = true,
RequireConsent = false
}
This is Startup in API service:
services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
.AddCookie()
.AddIdentityServerAuthentication(options =>
{
options.Authority = UrlsSettings.Identity;
options.RequireHttpsMetadata = true;
options.ApiName = Application.ToString();
options.ApiSecret = "test-secret";
options.SaveToken = true;
})
.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo
{
Title = "Merchant API",
Version = "v1",
Description = "eSales Platform API"
});
options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
{
Type = SecuritySchemeType.OAuth2,
Flows = new OpenApiOAuthFlows()
{
AuthorizationCode = new OpenApiOAuthFlow
{
AuthorizationUrl = new Uri(UrlsSettings.Identity + "/connect/authorize"),
TokenUrl = new Uri(UrlsSettings.Identity + "/connect/token"),
Scopes = new Dictionary<string, string>
{
{ Applications.API.ToString(), "eSalesPlatform API - full access" }
}
}
}
});
options.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "oauth2" }
},
new[] { Applications.API.ToString() }
}
});
options.OperationFilter<AuthorizeCheckOperationFilter>();
});

Related

IdentityServer4: Google sign-in only show OpenID scope and didn't show the email and profile scope information

When i'm trying to sign-in with google sign-in from blazor webassembly, the requested claims and the claims returned from identity server is not matching (see output)
BackEnd/Config.cs
namespace BackEnd
{
public static class ServerConfiguration
{
public static IEnumerable<IdentityResource> IdentityResources =>
new IdentityResource[]
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResources.Email(),
new IdentityResource("roles", "User roles", new List<string> { "role" })
};
public static IEnumerable<ApiScope> ApiScopes =>
new ApiScope[]
{
new ApiScope("protectedScope", "Protected Scope")
};
public static IEnumerable<Client> Clients =>
new Client[]
{
new Client()
{
ClientId = <confidential>,
ClientName = "client 1",
RequireClientSecret = false,
RequirePkce = true,
AllowedCorsOrigins = { "https://localhost:5001" },
AllowedGrantTypes = GrantTypes.Code,
RedirectUris = { "https://localhost:5001/authentication/login-callback" },
PostLogoutRedirectUris = { "https://localhost:5001/" },
AllowOfflineAccess = true,
AllowedScopes = new List<string>{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email,
"protectedScope"
}
},
new Client()
{
ClientId = "blazor",
ClientName = "oidcUser",
RequireClientSecret = false,
RequirePkce = true,
RequireConsent = true,
AllowedCorsOrigins = { "https://localhost:5001" },
AllowedGrantTypes = GrantTypes.Code,
RedirectUris = { "https://localhost:5001/authentication/login-callback" },
PostLogoutRedirectUris = { "https://localhost:5001/" },
AllowOfflineAccess = true,
AlwaysIncludeUserClaimsInIdToken = true,
AllowedScopes = new List<string>{
"openid",
"email",
"profile",
"protectedScope"
}
}
};
public static List<TestUser> TestUsers {
get
{
TestUser user1 = new TestUser()
{
SubjectId = "2f47f8f0-bea1-4f0e-ade1-88533a0eaf57",
Username = "John",
Claims = new List<Claim>()
{
new Claim("role", "SignedInUser"),
new Claim("email", "johnsmith#gmail.com"),
new Claim("picture", "https://www.google.com/url?sa=i&url=https%3A%2F%2Fwww.business2community.com%2Fsocial-media%2Fimportance-profile-picture-career-01899604&psig=AOvVaw2LC5T-WZMYnHD9I7PeK7lT&ust=1615219065948000&source=images&cd=vfe&ved=2ahUKEwip1caGxp7vAhV1NbcAHd_2BFwQjRx6BAgAEAc")
}
};
List<TestUser> testUsers = new List<TestUser>();
testUsers.Add(user1);
return testUsers;
}
}
}
}
BackEnd/Startup.cs
namespace BackEnd
{
public class Startup
{
public IWebHostEnvironment Environment { get; }
public IConfiguration Configuration { get; }
private string _clientId = null;
private string _clientSecret = null;
public Startup(IWebHostEnvironment environment, IConfiguration configuration)
{
Environment = environment;
Configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
var cert = new X509Certificate2(Path.Combine(".", "IdsvCertificate.pfx"), "YouShallNotPass123");
_clientId = Configuration["OAuth:ClientId"];
_clientSecret = Configuration["OAuth:ClientSecret"];
services.AddControllersWithViews();
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlite(Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = false)
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddTransient<IProfileService, ProfileService>();
var builder = services.AddIdentityServer(options =>
{
options.Events.RaiseErrorEvents = true;
options.Events.RaiseInformationEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseSuccessEvents = true;
// see https://identityserver4.readthedocs.io/en/latest/topics/resources.html
options.EmitStaticAudienceClaim = true;
options.UserInteraction = new UserInteractionOptions() { LoginUrl = "/Account/Login", LogoutUrl = "/Account/Logout" };
})
.AddInMemoryIdentityResources(Config.IdentityResources)
.AddInMemoryApiScopes(Config.ApiScopes)
.AddInMemoryClients(Config.Clients)
.AddProfileService<ProfileService>()
.AddAspNetIdentity<ApplicationUser>();
builder.AddSigningCredential(cert);
// builder.AddDeveloperSigningCredential();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddAuthentication()
.AddGoogle("Google", options =>
{
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
options.ClientId = _clientId;
options.ClientSecret = _clientSecret;
options.ClaimActions.MapJsonKey("picture", "picture", "url");
options.SaveTokens = true;
})
.AddOpenIdConnect("oidc", options =>
{
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
options.Authority = "https://accounts.google.com";
options.RequireHttpsMetadata = true;
options.ResponseType = "code";
options.Scope.Add("profile");
options.Scope.Add("email");
options.Scope.Add("openid");
options.ClientId = _clientId;
options.ClientSecret = _clientSecret;
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.ClaimActions.MapJsonKey("picture", "picture", "url");
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
RoleClaimType = "role",
ValidateIssuer = true
};
});
services.AddAuthorization();
services.AddGrpc();
}
public void Configure(IApplicationBuilder app)
{
if (Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseStaticFiles();
app.UseRouting();
app.UseIdentityServer();
app.UseAuthentication();
app.UseAuthorization();
app.UseGrpcWeb(new GrpcWebOptions { DefaultEnabled = true });
app.UseEndpoints(endpoints =>
{
endpoints.MapGrpcService<UserService>();
endpoints.MapDefaultControllerRoute().RequireAuthorization();
});
}
}
}
BackEnd/ProfileService.cs
public class ProfileService : IProfileService
{
public ProfileService()
{
}
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
var roleClaims = context.Subject.FindAll(JwtClaimTypes.Role);
context.IssuedClaims.AddRange(roleClaims);
await Task.CompletedTask;
}
public async Task IsActiveAsync(IsActiveContext context)
{
await Task.CompletedTask;
}
}
FrontEnd/Program.cs
namespace FrontEnd
{
public class Program
{
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.Services.AddScoped(sp => new HttpClient()
{ BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
builder.Services.AddScoped(services =>
{
var httpHandler = new GrpcWebHandler(GrpcWebMode.GrpcWeb, new HttpClientHandler());
var channel = GrpcChannel.ForAddress("https://localhost:5000", new GrpcChannelOptions
{
HttpHandler = httpHandler
});
return new Greeter.GreeterClient(channel);
});
builder.Services.AddScoped(services =>
{
var baseAddressMessageHandler = services.GetRequiredService<BaseAddressAuthorizationMessageHandler>();
baseAddressMessageHandler.InnerHandler = new HttpClientHandler();
var httpHandler = new GrpcWebHandler(GrpcWebMode.GrpcWeb, new HttpClientHandler());
var channel = GrpcChannel.ForAddress("https://localhost:5000", new GrpcChannelOptions
{
HttpHandler = httpHandler
});
return new User.UserClient(channel);
});
builder.Services.AddOidcAuthentication(options =>
{
builder.Configuration.Bind("Authentication:Google", options.ProviderOptions);
options.UserOptions.RoleClaim = "SignedInUser";
}).AddAccountClaimsPrincipalFactory<CustomUserFactory>();
builder.Services.AddOptions();
builder.Services.AddAuthorizationCore();
await builder.Build().RunAsync();
}
}
FrontEnd/wwwroot/appsettings.json
{
"Authentication":{
"Google": {
"Authority": "https://localhost:5000",
"ClientId": <confidential>,
"ClientSecret": "2fxc9srOe8QsRBnhzLIa1pF0",
"DefaultScopes": [
"email",
"profile",
"openid"
],
"PostLogoutRedirectUri": "https://localhost:5001/",
"RedirectUri": "https://localhost:5001/authentication/login-callback",
"ResponseType": "code"
},
}
This is the output from IdentityServer when i'm trying to sign-in
[18:34:28 Debug] IdentityServer4.Validation.TokenValidator
Calling into custom token validator: IdentityServer4.Validation.DefaultCustomTokenValidator
[18:34:28 Debug] IdentityServer4.Validation.TokenValidator
Token validation success
{"ClientId": null, "ClientName": null, "ValidateLifetime": true, "AccessTokenType": "Jwt", "ExpectedScope": "openid", "TokenHandle": null, "JwtId": "8E6167D64F8FEA2FF6D12D17A1CEEBFE", "Claims": {"nbf": 1615548868, "exp": 1615552468, "iss": "https://localhost:5000", "aud": "https://localhost:5000/resources", "client_id": "499675830263-ldcg4fm7kcbjlt48tpaffqdbfnskmi8v.apps.googleusercontent.com", "sub": "81c306df-c1f0-4714-964f-2459b670429e", "auth_time": 1615548849, "idp": "oidc", "jti": "8E6167D64F8FEA2FF6D12D17A1CEEBFE", "sid": "0BF0BA4CA6BD3DE8D158A426A70A91E0", "iat": 1615548868, "scope": ["openid", "profile", "email", "role"], "amr": "external"}, "$type": "TokenValidationLog"}
[18:34:28 Debug] IdentityServer4.ResponseHandling.UserInfoResponseGenerator
Creating userinfo response
[18:34:28 Debug] IdentityServer4.ResponseHandling.UserInfoResponseGenerator
Scopes in access token: openid profile email role
[18:34:28 Debug] IdentityServer4.ResponseHandling.UserInfoResponseGenerator
Requested claim types: sub name family_name given_name middle_name nickname preferred_username profile picture website gender birthdate zoneinfo locale updated_at email email_verified role
[18:34:28 Information] IdentityServer4.ResponseHandling.UserInfoResponseGenerator
Profile service returned the following claim types: sub name preferred_username
[18:34:28 Debug] IdentityServer4.Endpoints.UserInfoEndpoint
End userinfo request
[18:34:29 Debug] IdentityServer4.Hosting.EndpointRouter
Request path /connect/checksession matched to endpoint type Checksession
[18:34:29 Debug] IdentityServer4.Hosting.EndpointRouter
Endpoint enabled: Checksession, successfully created handler: IdentityServer4.Endpoints.CheckSessionEndpoint
[18:34:29 Information] IdentityServer4.Hosting.IdentityServerMiddleware
Invoking IdentityServer endpoint: IdentityServer4.Endpoints.CheckSessionEndpoint for /connect/checksession
[18:34:29 Debug] IdentityServer4.Endpoints.CheckSessionEndpoint
Rendering check session result
From the output, the requested claims and the claims that's returned by the profile service is not matching
From the image, there should be a name after the "hello"
The image is similar except now it shows the name. IdentityServer4 is working fine, it shows everything as i expected
By default, only these claims are mapped from the ID-Token:
options.ClaimActions.MapUniqueJsonKey("sub", "sub");
options.ClaimActions.MapUniqueJsonKey("name", "name");
options.ClaimActions.MapUniqueJsonKey("given_name", "given_name");
options.ClaimActions.MapUniqueJsonKey("family_name", "family_name");
options.ClaimActions.MapUniqueJsonKey("profile", "profile");
options.ClaimActions.MapUniqueJsonKey("email", "email");
To get additional claims mapped, we have to write like:
options.ClaimActions.MapUniqueJsonKey("website", "website");
options.ClaimActions.MapUniqueJsonKey("gender", "gender");
options.ClaimActions.MapUniqueJsonKey("birthdate", "birthdate");
In AddOpenIDConnect. This is called Claims transformation if you want to google for it.

IdentityServer4 Delegation With Windows Authentication User.Identity.Name is null

I have the following flow Asp.Net Core App (App) that calls Asp.Net Core Web API (API1) that inturn calls another Asp.Net Web API (API2)
I'm using IdentityServer4 with Windows Authentication to Authenticate my users
here is my code:
ApiResources definition:
new ApiResource("api1", "api1", new List<string> { JwtClaimTypes.Name, JwtClaimTypes.Email}),
new ApiResource("api2", "api2", new List<string> { JwtClaimTypes.Name, JwtClaimTypes.Email})
Clients definition
new Client
{
ClientId = "app1",
ClientName = "app1",
ClientSecrets = { new Secret("app1".Sha256())},
AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.OfflineAccess,
IdentityServerConstants.StandardScopes.Email,
"api1"
},
RedirectUris = { "https://localhost:44375/signin-oidc" },
FrontChannelLogoutUri = "https://localhost:44375/signout-oidc",
PostLogoutRedirectUris = { "https://localhost:44375/signout-callback-oidc" },
AllowOfflineAccess = true,
RequireConsent = false,
AccessTokenLifetime = 5
},
new Client
{
ClientId = "api1",
ClientSecrets = { new Secret("api1".Sha256())},
AllowedGrantTypes = {"delegation" },
AllowedScopes = {
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email,
"api2"}
}
Delegation code in API1
public async Task<string> DelegateAsync(string userToken)
{
var client = new HttpClient();
var disco = await client.GetDiscoveryDocumentAsync("https://localhost:44382/");
if (disco.IsError) throw new Exception(disco.Error);
var tokenResponse = await client.RequestTokenAsync(new TokenRequest()
{
Address = disco.TokenEndpoint,
GrantType = "delegation",
ClientId = "api1",
ClientSecret = "api1",
Parameters =
{
{"scope" , "api2 email profile openid" },
{"token", userToken }
}
});
if (tokenResponse.IsError)
{
throw new Exception(tokenResponse.Error);
}
_logger.LogInformation($"new: {tokenResponse.AccessToken}");
return tokenResponse.AccessToken;
}
IdentityServer4 DelegationGrantValidator:
public class DelegationGrantValidator : IExtensionGrantValidator
{
private readonly ITokenValidator _validator;
public DelegationGrantValidator(ITokenValidator validator)
{
_validator = validator;
}
public string GrantType => "delegation";
public async Task ValidateAsync(ExtensionGrantValidationContext context)
{
var userToken = context.Request.Raw.Get("token");
if (string.IsNullOrEmpty(userToken))
{
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant);
return;
}
var result = await _validator.ValidateAccessTokenAsync(userToken);
if (result.IsError)
{
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant);
return;
}
// get user's identity
var sub = result.Claims.FirstOrDefault(c => c.Type == "sub").Value;
context.Result = new GrantValidationResult(sub, GrantType);
return;
}
}
in API1 User.Identity.Name = "Domain\UserName"
but in API2 User.Identity.Name = null
is there anything missing that I should do solve this issue?
P.S.: if I call the IdentityServer UserInfo endpoint from API2 I'll get the expected UserName
After a lot of search I have finally found the answer.
I've followed what Rory Braybrook described in this Article and everything is working now

How to combine AddOpenIdConnect() and AddIdentityServerAuthentication() in one resource server?

Can somebody help me with my current configuration for protecting our server using Identity Server 4, currently I'm using package IdentityServer4 2.3.0. I found that when I hit one of my api with a valid token, it always return 401 Unauthorized or 302 Found. My comment on listing below show my problem:
services
.AddAuthentication()
.AddOpenIdConnect(
"oidc",
"OpenID Connect",
x =>
{
x.Authority = "https://localhost:44378"; // Try to set breakpoint here, it hitted.
x.SignInScheme = "Cookies";
x.ClientId = "myclient;
x.SaveTokens = true;
x.GetClaimsFromUserInfoEndpoint = true;
x.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
RoleClaimType = "role"
};
})
.AddIdentityServerAuthentication(
"Bearer",
x =>
{
x.Authority = "https://localhost:44378"; // Try to set breakpoint here, not hitted.
x.ApiName = "api1";
x.ApiSecret = "apisecret";
x.RequireHttpsMetadata = true;
})
;
Here's an example of how I do it to get the Hybrid flow working:
services
.AddAuthentication(
(options) =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie(
(options) =>
{
options.AccessDeniedPath = new PathString("/home/accessdenied");
})
.AddOpenIdConnect(
"oidc",
(options) =>
{
options.SignInScheme = "Cookies";
options.Authority = applicationConfiguration.IdentityServerBaseUri;
options.RequireHttpsMetadata = false;
options.ClientId = "<id>";
options.ClientSecret = "<secret>";
options.ResponseType = "code id_token";
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("lithium-datalookup-vatnumber");
options.Scope.Add("offline_access");
options.Scope.Add("profile");
options.Scope.Add("email");
options.Scope.Add("subscription");
});

IdentityServer4 IsValidReturnUrl returns false for valid url

I've a test project with this client configuration:
public class Clients : IClientStore
{
public Task<Client> FindClientByIdAsync(string clientId)
{
return Task.FromResult(new Client
{
ClientId = "client.webforms",
ClientName = "WebForms Client",
AllowedGrantTypes = GrantTypes.Hybrid,
AllowAccessTokensViaBrowser = false,
ClientSecrets =
{
new Secret("1234".Sha256())
},
RedirectUris = { "http://localhost:9869/signin-oidc" },
PostLogoutRedirectUris = { "http://localhost:9869/" },
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
CstIdSrvScopeTypes.TestWebForms
},
AllowOfflineAccess = false,
RequireConsent = false,
AlwaysIncludeUserClaimsInIdToken = true
});
}
}
When I try to validate it in LoginController I'm getting false result (this is from Immediate Window:
returnUrl
"http://localhost:9869/signin-oidc"
this.identityServer.IsValidReturnUrl(returnUrl)
false
Also this.identityServer.GetAuthorizationContextAsync(returnUrl) result is null. Am I doing something wrong?
Yes - you need to add a single RedirectUri when you configur your client that is one of the RedirectUris that is in the list you have above.
Something like
#
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
SignInAsAuthenticationType = Settings.AuthenticationType,
Authority = config.Authority,
RedirectUri = config.RedirectUri
}

Refresh ClaimsPrincipal with OpenIDConnect Middleware, MVC Client, Cookies

Is there a standard way to ask an MVC client using ID4 OIDC middleware to refresh the ClaimsPrincipal when using cookies? It would be nice to just ask the middleware to refresh the ClaimsPrincipal but I don't think that functionality exists.
The code below does work, however, there is no nonce used in the example below - so I'm not sure if that's secure. I'm not sure how the middleware creates the nonce.
Does anyone have an example of properly refreshing the ClaimsPrincipal in an MVC client application using cookies with ID4 OIDC middleware?
Validate ID token and Return ClaimsPrincipal from ID Token
private ClaimsPrincipal ValidateIdentityToken(string idToken, DiscoveryResponse disco )
{
var keys = new List<SecurityKey>();
foreach (var webKey in disco.KeySet.Keys)
{
var e = Base64Url.Decode(webKey.E);
var n = Base64Url.Decode(webKey.N);
var key = new RsaSecurityKey(new RSAParameters { Exponent = e, Modulus = n });
key.KeyId = webKey.Kid;
keys.Add(key);
}
var parameters = new TokenValidationParameters
{
ValidIssuer = disco.TryGetString(OidcConstants.Discovery.Issuer),
ValidAudience = "mvc.hybrid",
IssuerSigningKeys = keys,
NameClaimType = JwtClaimTypes.Name,
RoleClaimType = JwtClaimTypes.Role
};
var handler = new JwtSecurityTokenHandler();
handler.InboundClaimTypeMap.Clear();
SecurityToken token;
var user = handler.ValidateToken(idToken, parameters, out token);
//var nonce = user.FindFirst("nonce")?.Value ?? "";
//if (!string.Equals(nonce, "random_nonce")) throw new Exception("invalid nonce");
//nonce is always ""
return user;
}
Check cookie expiration. Use the refresh token to refresh ID and Access token. Use the above validation to return ClaimsPrincipal
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme = "Cookies",
AutomaticAuthenticate = true,
ExpireTimeSpan = TimeSpan.FromMinutes(60),
Events = new CookieAuthenticationEvents()
{
OnValidatePrincipal = async cookiecontext =>
{
if (cookiecontext.Properties.Items.ContainsKey(".Token.expires_at"))
{
var expire = DateTime.Parse(cookiecontext.Properties.Items[".Token.expires_at"]);
if (expire <= DateTime.Now.AddMinutes(-5) || DateTime.Now > expire)
{
var disco = await DiscoveryClient.GetAsync("http://localhost:5000");
if (disco.IsError) throw new Exception(disco.Error);
var refreshToken = cookiecontext.Properties.Items[".Token.refresh_token"];
var tokenClient = new TokenClient(disco.TokenEndpoint,
"mvc.hybrid",
"secret");
var response = await tokenClient.RequestRefreshTokenAsync(refreshToken);
if (!response.IsError)
{
cookiecontext.Properties.Items[".Token.access_token"] = response.AccessToken;
cookiecontext.Properties.Items[".Token.refresh_token"] = response.RefreshToken;
cookiecontext.Properties.Items[".Token.expires_at"] = DateTime.Now.AddSeconds((int)response.ExpiresIn).ToString();
cookiecontext.Properties.Items["NextAccessTokenRefresh"] = DateTime.Now.AddMinutes(5).ToString();
var _Princ = ValidateIdentityToken(response.IdentityToken, disco);
cookiecontext.ReplacePrincipal(_Princ);
cookiecontext.ShouldRenew = true;
}
else
{
cookiecontext.RejectPrincipal();
}
}
}
}
}
});
Wireup ID4 middleware
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
AuthenticationScheme = "oidc",
SignInScheme = "Cookies",
Authority = "http://localhost:5000",
RequireHttpsMetadata = false,
ClientId = "mvc.hybrid",
ClientSecret = "secret",
ResponseType = "code id_token",
Scope = { "openid", "profile", "email", "api1", "offline_access", "role" },
GetClaimsFromUserInfoEndpoint = true,
Events = new OpenIdConnectEvents()
{
OnTicketReceived = notification =>
{
notification.Response.Cookies.Append("NextAccessTokenRefresh", DateTime.Now.AddMinutes(5).ToString());
return Task.FromResult(0);
}
},
SaveTokens = true,
TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
NameClaimType = JwtClaimTypes.Name,
RoleClaimType = JwtClaimTypes.Role,
}
});
}

Resources