Here is my situation:
SS API A:
1-Still at v6.5.0 and stuck on .Net Framework 4.7.2
2-Uses protoc-only generated Service Client to call SS API B.
3-Uses interceptor to use set Bearer with API Key provided by SS API B.
4-Doesn’t use Client Certificate.
5-Calls SS API B endpoints on secured https 5051
SS API B:
1-.Net 8 Kestrel windows service listening on secured https gRPC 5051 and Https 5001
2-Uses gRPC, ASP.Net Core IdentityAuth and API Keys features.
3-Services must be protected so client must be authenticated and have Admin role.
Everything works great except when I want to setup the authentication/authorization.
I have to admit that I am very confused on what I have to do on the ASP.NET core side vs SS when it comes to do the authentication and authorization.
Currently, using the API Explorer (/ui) and the default users or the standalone API key I created for API A, it works fine from the UI. But when calls are made using the same API Key from my gRPC Client on API A, it doesn’t. API A has the correct Authentication Bearer header API key on each request but how API B is going to create the custom SS session from it? Should I add services.AddAuthentication().something?
Is this something with the service attributes ( I have tried a bunch of combinations), should those be on the requests instead?
Any help would be much appreciated.
API A :
private readonly Lazy<GrpcServices.GrpcServicesClient> _client = new(() => new GrpcServices.GrpcServicesClient(
GrpcChannel.ForAddress($"https://{Connection.HostName}:{Connection.HostPort}", new GrpcChannelOptions {
HttpClient = new System.Net.Http.HttpClient(new WinHttpHandler() {
ServerCertificateValidationCallback = (req, cert, certChain, sslErrors) =>
cert.SubjectName.RawData.SequenceEqual(cert.IssuerName.RawData) && // self-signed
cert.GetNameInfo(X509NameType.DnsName, forIssuer:false) == Connection.HostName &&
(sslErrors & ~SslPolicyErrors.RemoteCertificateChainErrors) == SslPolicyErrors.None // only this
}),
Credentials = ChannelCredentials.Create( new SslCredentials(), GetCredentials())
})));
private static CallCredentials GetCredentials()
{
var credentials = CallCredentials.FromInterceptor(async (context, metadata) =>
{
metadata.Add("Authorization", $"Bearer {Connection.ApiKey}");
});
return credentials;
}
…
Client gets error :
Status(StatusCode=“Unauthenticated”, Detail=“Unauthorized”)
when calling the endpoint
API B:
builder.Services.AddAuthorization();
builder.Services.AddIdentity<ApplicationUser, IdentityRole>(options => {
options.SignIn.RequireConfirmedAccount = true;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddSignInManager()
.AddDefaultTokenProviders();
builder.Services.ConfigureApplicationCookie(options => options.DisableRedirectsForApis());
builder.Services.AddScoped<IUserClaimsPrincipalFactory<ApplicationUser>, AdditionalUserClaimsPrincipalFactory>();
...
[Authenticate]
[ValidateIsAuthenticated]
[RequiredRole("Admin")]
public class UAService: Service{}
…
Inside the service request handler, the session is NULL:
public async Task<UAWriteResponse> Any(UAWriteMultiValuesRequest request)
{
var session = await GetSessionAsync() as CustomUserSession;
..
}
When dumping the base.Request:
Items:
{
__AuthorizationMiddlewareWithEndpointInvoked: {},
__apikey:
{
__type: "ServiceStack.ApiKeysFeature+ApiKey, ServiceStack.Server",
id: 3,
key: full key displayed correctly here,
name: test,
visibleKey: ak-***72b,
createdDate: 2024-11-07T17:40:31.0111292-05:00,
lastUsedDate: 2024-11-13T16:16:39.8563099Z,
scopes:
[
Admin
],
features: [],
restrictTo: [],
notes: test notes
},
__session:
{
__type: "ServiceStack.AuthUserSession, ServiceStack",
id: 361bb9244b5044479b6a5408a96636b4,
userAuthId: 0,
userAuthName: authsecret,
userName: authsecret,
displayName: Admin,
createdAt: 0001-01-01,
lastModified: 0001-01-01,
roles:
[
Admin
],
permissions: [],
isAuthenticated: True,
fromToken: False,
tag: 0,
authProvider: authsecret,
providerOAuthAccess: [],
meta: {}
}