GrpcChannelOptions accepts both HttpHandler and HttpClient
SocketsHttpHandler.EnableMultipleHttp2Connections creates new HTTP/2 connections when needed.
And HttpClient internally creates new connections when it is required (as I understand).
GrpcChannelOptions _options = new GrpcChannelOptions()
{
HttpHandler = new SocketsHttpHandler
{
EnableMultipleHttp2Connections = true, // additional HTTP/2 connections are created by a channel when the concurrent stream limit is reached.
PooledConnectionIdleTimeout = TimeSpan.FromSeconds(30),
KeepAlivePingDelay = TimeSpan.FromSeconds(40),
KeepAlivePingTimeout = TimeSpan.FromSeconds(20),
},
DisposeHttpClient = true,
HttpClient = new HttpClient(new HttpClientHandler()
{
ServerCertificateCustomValidationCallback = (HttpRequestMessage _, X509Certificate2? _, X509Chain? _, SslPolicyErrors _) =>
{
return true;
},
UseProxy = false,
})
{
DefaultRequestVersion = new Version(2, 0),
}
};
I have a singlton GrpcChannel creating with above option. is it ok to configure both fields as above?
Related
I have an API with authorization code flow authentication and I have configured swagger to use that as a security definition and it works fine.
Now I need swagger to send the bearer token in a different header as well, besides "Authorization", e.g. "X-Forwarded-Authorization". Is there a way to do that?
My current security configuration:
setup.AddSecurityDefinition(
"oauth2", new OpenApiSecurityScheme
{
Type = SecuritySchemeType.OAuth2,
Flows = new OpenApiOAuthFlows
{
AuthorizationCode = new OpenApiOAuthFlow
{
AuthorizationUrl = new Uri("..."),
TokenUrl = new Uri("..."),
Scopes = { }
}
},
});
You can configure swagger when adding service collection like this;
services.AddSwaggerGen(options =>
{
//...other configurations
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
In = ParameterLocation.Header,
Description = "Please insert JWT with Bearer into field!",
Name = "X-Forwarded-Authorization",
Type = SecuritySchemeType.ApiKey
});
options.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
}, Array.Empty<string>()
}
});
//...other configurations
});
Currently I have three separate servers. Client on :5001, API on :5002 and IdentityServer on :5003. I can athenticate my Blazor pages using #attribute [Authorize] but when I call the API I get a 401 error. If I past the token_id into postman and make a request to the API server it authenticates. If I make a request from my Blazor client it fails. I have whitelisted CORS to rule out that being the issue. If I remove the Audience check on the api with:
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false
};
It works
Client program.cs
builder.Services.AddHttpClient("api")
.AddHttpMessageHandler(sp =>
{
var handler = sp.GetService<AuthorizationMessageHandler>()
.ConfigureHandler(
authorizedUrls: new[] { "https://localhost:5002" },
scopes: new[] { "coredesk" });
return handler;
});
builder.Services.AddScoped(
sp => sp.GetService<IHttpClientFactory>().CreateClient("api"));
builder.Services.AddOidcAuthentication(options =>
{
builder.Configuration.Bind("oidc", options.ProviderOptions);
});
Client appsettings.json
{
"oidc": {
"Authority": "https://localhost:5003/",
"ClientId": "coredesk",
"DefaultScopes": [
"openid",
"profile",
"coredesk"
],
"PostLogoutRedirectUri": "/",
"ResponseType": "code"
}
}
API Startup.cs
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = "https://localhost:5003";
options.Audience = "coredesk";
});
IdentityServer Config.cs
public static IEnumerable<IdentityResource> IdentityResources =>
new IdentityResource[]
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
};
public static IEnumerable<ApiResource> Apis =>
new ApiResource[]
{
new ApiResource("coredesk", "CoreDesk API")
};
public static IEnumerable<ApiScope> ApiScopes =>
new ApiScope[]
{
new ApiScope("coredesk"),
};
public static IEnumerable<Client> Clients =>
new Client[]
{
new Client
{
ClientId = "coredesk",
AllowedGrantTypes = GrantTypes.Code,
RequirePkce = true,
RequireClientSecret = false,
AllowedCorsOrigins = { "https://localhost:5001", "https://localhost:5002" },
AllowedScopes = { "openid", "profile", "coredesk" },
RedirectUris = { "https://localhost:5001/authentication/login-callback" },
PostLogoutRedirectUris = { "https://localhost:5001/" },
Enabled = true
},
};
if you look at the source code for AddJwtBearer you find this part:
// If no authorization header found, nothing to process further
if (string.IsNullOrEmpty(authorization))
{
return AuthenticateResult.NoResult();
}
if (authorization.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
{
token = authorization.Substring("Bearer ".Length).Trim();
}
// If no token found, no further work possible
if (string.IsNullOrEmpty(token))
{
return AuthenticateResult.NoResult();
}
this shows that by default (without customization) it requires the token to be provided in the Authorization header, like this:
GET /api/payments HTTP/1.1
Host: localhost:7001
Authorization: Bearer eyJhbGciOiJSUzI1NiIsIYwA...
to further debug, I would remove the authorize attribute and then put a breakpoint in one of the action method and examine what the ClaimsPrincipal user object contains.
I am working on a blazor application where I used my API project as Identity
Provider. Everything is working fine but the issue is that the access token
issued by my API is not validated by the API. It turns out the API is expecting a
cookie header. I took a closer look at blazor hosted application and found out
the cookie is being sent along with each request but it's same-origin.
My Blazor WASM project does not automatically attach this cookie in the request
header, just the access token.
Is there a way I can make the Http handler attach this cookie on each request?
or make the API validate the access token instead of the identity cookie.
This is my startup class in the API Project
public static void AddIdentityServer(IServiceCollection services,IConfiguration configuration)
{
services.AddIdentityServer(options =>
{
options.UserInteraction.LoginUrl = "/Identity/Account/Login";
options.UserInteraction.LogoutUrl = "/Identity/Account/Logout";
}).AddProfileService<LocalProfileService>()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>(option =>
{
option.Clients.Add(new Client
{
ClientId = "blazor",
AllowedGrantTypes = GrantTypes.Code,
RequirePkce = true,
RequireClientSecret = false,
AllowedCorsOrigins = { "https://localhost:5001" },
AllowedScopes = { "openid", "profile", "email","id" },
RedirectUris = { "https://localhost:5001/authentication/login-callback" },
PostLogoutRedirectUris = { "https://localhost:5001/" },
Enabled = true,
RequireConsent = false,
});
option.IdentityResources.AddEmail();
option.IdentityResources["openid"].UserClaims.Add("name");
option.ApiResources.Single().UserClaims.Add("name");
option.IdentityResources["openid"].UserClaims.Add("role");
option.ApiResources.Single().UserClaims.Add("role");
option.IdentityResources.Add(new IdentityResource("id",new string[] {"id" }));
option.ApiResources.Single().UserClaims.Add("id");
});
services.AddAuthentication()
.AddGoogle("Google", options =>
{
options.ClientId = configuration["ExternalLoginApiKey:GoogleClientId"];
options.ClientSecret = configuration["ExternalLoginApiKey:GoogleClientSecret"];
})
.AddFacebook("Facebook", options =>
{
options.AppId = configuration["ExternalLoginApiKey:FacebookAppId"];
options.AppSecret = configuration["ExternalLoginApiKey:FacebookAppSecret"];
})
.AddIdentityServerJwt();
}
Program class in the Blazor Project
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("app");
builder.Services.AddOidcAuthentication(options =>
{
builder.Configuration.Bind("oidc", options.ProviderOptions);
options.UserOptions.RoleClaim = "role";
}).AddAccountClaimsPrincipalFactory<CustomUserFactory>();
builder.Services.AddHttpClient<IAuthorizedRestService, AuthorizedRestService>(
client => client.BaseAddress = new Uri("https://localhost:5002/api/mart/v1/"))
.AddHttpMessageHandler(sp => sp.GetRequiredService<AuthorizationMessageHandler>()
.ConfigureHandler(authorizedUrls: new[] { "https://localhost:5002" }));
builder.Services.AddHttpClient("noauth", option => option.BaseAddress = new
Uri("https://localhost:5002/api/mart/v1/"));
builder.Services.AddScoped<IRestService, RestService>();
await builder.Build().RunAsync();
}
I have found the Solution.
It happens that there is already a JWT handler provided by IdentityServer4 for APIs that double as Authorization Server
.AddIdentityServerJwt();
So what I did was to configure it
services.Configure<JwtBearerOptions>
(IdentityServerJwtConstants.IdentityServerJwtBearerScheme,
options =>
{
options.Authority = "https://localhost:5002";
options.Audience = "mart";
options.SaveToken = true;
});
Then specify the Authentication scheme to use
[Authorize(AuthenticationSchemes = IdentityServerJwtConstants.IdentityServerJwtBearerScheme)]
You can also add it globally in the start up class
var authorizationPolicy = new AuthorizationPolicyBuilder(IdentityServerJwtConstants.IdentityServerJwtBearerScheme)
.RequireAuthenticatedUser().Build();
options.Filters.Add(new AuthorizeFilter(authorizationPolicy));
You can read more using these links
https://learn.microsoft.com/en-us/aspnet/core/security/authorization/limitingidentitybyscheme?view=aspnetcore-3.1
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/identity-api-authorization?view=aspnetcore-3.1
I am trying to integrate user authentication between an MVC 4.7.1 client and an ASP.NET Core 3.1 IdentityServer4 & ASP.NET Identity service.
I have been following this tutorial for cookie issued authentication: Refreshing your Legacy ASP.NET IdentityServer Client Applications (with PKCE)
So far, the MVC client is able to redirect to the Login page. Upon logging in, I have a null error in the following function:
private string RetrieveCodeVerifier(AuthorizationCodeReceivedNotification n)
{
string key = GetCodeVerifierKey(n.ProtocolMessage.State);
string codeVerifierCookie = n.Options.CookieManager.GetRequestCookie(n.OwinContext, key);
if (codeVerifierCookie != null)
{
var cookieOptions = new CookieOptions
{
SameSite = SameSiteMode.None,
HttpOnly = true,
Secure = n.Request.IsSecure
};
n.Options.CookieManager.DeleteCookie(n.OwinContext, key, cookieOptions);
}
string codeVerifier;
var cookieProperties = n.Options.StateDataFormat.Unprotect(Encoding.UTF8.GetString(Convert.FromBase64String(codeVerifierCookie)));
cookieProperties.Dictionary.TryGetValue("cv", out codeVerifier);
return codeVerifier;
}
Apparently the codeVerifierCookie is null.
The rest of the configuration is as follows.
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "cookie"
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
ClientId = "mvc.owin",
Authority = "https://localhost:44355",
RedirectUri = "http://localhost:5001/Auth/",
Scope = "openid profile scope1",
SignInAsAuthenticationType = "cookie",
RequireHttpsMetadata = false,
UseTokenLifetime = false,
RedeemCode = true,
SaveTokens = true,
ClientSecret = "secret",
ResponseType = "code",
ResponseMode = "query",
Notifications = new OpenIdConnectAuthenticationNotifications
{
RedirectToIdentityProvider = n =>
{
if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.Authentication)
{
// set PKCE parameters
var codeVerifier = CryptoRandom.CreateUniqueId(32);
string codeChallenge;
using (var sha256 = SHA256.Create())
{
var challengeBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(codeVerifier));
codeChallenge = Base64Url.Encode(challengeBytes);
}
n.ProtocolMessage.SetParameter("code_challenge", codeChallenge);
n.ProtocolMessage.SetParameter("code_challenge_method", "S256");
// remember code_verifier (adapted from OWIN nonce cookie)
RememberCodeVerifier(n, codeVerifier);
}
return Task.CompletedTask;
},
AuthorizationCodeReceived = n =>
{
// get code_verifier
var codeVerifier = RetrieveCodeVerifier(n);
// attach code_verifier
n.TokenEndpointRequest.SetParameter("code_verifier", codeVerifier);
return Task.CompletedTask;
}
}
});
And on IdentityServer4 ConfigureServices:
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
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;
})
.AddInMemoryIdentityResources(Config.IdentityResources)
.AddInMemoryApiScopes(Config.ApiScopes)
.AddInMemoryClients(Config.Clients)
.AddAspNetIdentity<ApplicationUser>();
Finally, Client.cs configuration on IdentityServer4 side:
new Client
{
ClientId = "mvc.owin",
ClientName = "MVC Client",
AllowedGrantTypes = GrantTypes.Code,
ClientSecrets = {new Secret("secret".Sha256())},
RedirectUris = {"http://localhost:5001/Auth/"},
AllowedScopes = {"openid", "profile", "scope1"},
AllowPlainTextPkce = false,
RequirePkce = true,
RequireConsent = false,
// Token lifetimes
AuthorizationCodeLifetime = 60,
AccessTokenLifetime = 60,
IdentityTokenLifetime = 60
}
AccountController and the rest is pretty much the basic IdentityServer4.Template, specifically is4aspid.
Anyone tried the same and knows what fails?
Is there a way to do it with JWT instead of Cookies? And, what are the drawbacks?
Edit: Apparently, this configuration works with Firefox, and I am suspecting this is a problem with Chrome's Same-Site cookie policy, hence the null in GetRequestCookie. The thing is, IdentityServer4 is running on HTTPS (otherwise, there are others) while the MVC client app is running on HTTP (note: both on localhost). I have tried using SameSite policy None, Lax, Strict and vise-versa with no success. I am not sure what else to try.
Best,
mkanakis.
Cookies that assert SameSite=None must also be marked as Secure, this means you need to use https
Read more here
I'm creating a Firefox add-on which contains server listening for TCP connections on one port. The problem is that it does not always close the connection: after sending a FIN-ACK, and receiving an ACK, the TCP session is left open if the client does not send in return a FIN-ACK.
Only some connections are not closed completely. But after a while, they are too many TC connections hanging, and Firefox cannot open an new file handle, or receive any new connection.
TCP localhost.localdomain:commtact-https->localhost.localdomain:46951 (CLOSE_WAIT)
I could not find a way, preferably in the add-on (but I also tried on the client side) to make sure all TCP connections are closed correctly. Here is what the server functionality looks like in the add-on:
init_server: function(port) {
server.result = {};
server.listener = {
onSocketAccepted : function(serverSocket, transport) {
server.result.sout = transport.openOutputStream(0,0,0);
var instream = transport.openInputStream(0,0,0);
server.result.sin = Components.classes["#mozilla.org/binaryinputstream;1"].
createInstance(Components.interfaces.nsIBinaryInputStream);
server.result.sin.setInputStream(instream);
server.debug("New incoming connection");
var dataListener = {
onStartRequest: function(request, context) {
server.debug("onStartRequest");
},
onStopRequest: function(request, context, status) {
server.debug("onStopRequest");
// instream.close();
// delete instream;
// server.result.sout.close();
// server.result.sin.close();
},
onDataAvailable: function(request, context, inputStream, offset, count) {
var sis = Components.classes ["#mozilla.org/scriptableinputstream;1"].createInstance (Components.interfaces.nsIScriptableInputStream);
sis.init(inputStream);
var str = sis.read(count);
var suc = Components.classes ["#mozilla.org/intlscriptableunicodeconverter"].createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
suc.charset = "utf-8";
var line = suc. ConvertToUnicode (str);
server.debug(line);
[ ... process data ... ]
// instream.close();
// server.result.sout.close();
// server.result.sin.close();
// delete instream;
},
};
// Listen to new data
var pump = Components.classes["#mozilla.org/network/input-stream-pump;1"].createInstance(Components.interfaces.nsIInputStreamPump);
pump.init(instream, -1, -1, 0, 0, false); //screenserver.result.sin
pump.asyncRead(dataListener, null);
},
onStopListening : function(serverSocket, status){
server.debug("Client dead?");
server.init_server(server.PORT);
},
};
server.socket = null;
server.socket = Components.classes["#mozilla.org/network/server-socket;1"].getService(Components.interfaces.nsIServerSocket);
server.socket.init(port, true, -1);
server.socket.asyncListen(server.listener);
}
I think you need to save the transport you're passed in call onSocketAccepted and then invoke close() on that.