Blazor WASM IHttpClientFactory.CreateClient(..) returns client without JWT token in headers - jwt

I have a Blazor client app that needs to communicate to multiple API services. In order to simplify things, lets say I have two API servers:
one API server that handles login and register services (https://localhost:7031/api/...)
one server that serves Blazor ASP.NET Core hosted app (https://localhost:7030).
Below I show how I configured identity services on 7031 (I am not using Identity Server):
services.AddIdentity<AppUser, IdentityRole<Guid>>(...)
.AddEntityFrameworkStores<AuthDbContext>()
.AddDefaultTokenProviders();
services.AddAuthenticationWithJwt(configuration); // extension method that configures JWT (not shown here)
services.AddAuthorization(options =>
{
options.AddIdentityPolicies();
options.FallbackPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme)
.Build();
});
To cut things short, login / register works. Web app is served on port 7030, and authenticates using API's hosted on port 7031. I have also created a test API endpoint with [Authorize] on 7031, to see if I token will be attached to this HTTPS request - all is well. For this to work, I have the following configuration for HttpClient:
services.AddHttpClient(
"IdentityApi",
client => client.BaseAddress = new Uri("https://localhost:7031"));
services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("IdentityApi"));
So, when I inject HttpClient in some component, it successfully connects to authorized identity APIs. The problem starts when I need to connect to authorized base server APIs (port 7030). In one component I need to access both identity API and server API, so I tried by injecting IHttpClientFactory in it. For this purpose I also added another named HTTPClient:
services.AddHttpClient(
"BaseApi",
client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)); //"https://localhost:7030"
/* razor component */
var identityClient = HttpClientFactory.CreateClient("IdentityApi);
var baseClient = HttpClientFactory.CreateClient("BaseApi);
Both clients have NULL value for DefaultRequestHeaders property. Only if HttpClient is injected directly, then it has DefaultRequestHeaders set, along with Authorization token.
So, somehow this line makes sure that when HttpClient is injected, it has token assigned:
services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("IdentityApi"));
I tried to use CustomAuthorizationMessageHandler, as suggested here, but I receive exception:
System.InvalidOperationException: Unable to resolve service for type 'Microsoft.AspNetCore.Components.WebAssembly.Authentication.IAccessTokenProvider' while attempting to activate 'x.y.z.CustomAuthorizationMessageHandler'.
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateArgumentCallSites(Type implementationType, CallSiteChain callSiteChain, ParameterInfo[] parameters, Boolean throwIfCallSiteNotFound) ... etc
Anyone knows how can I generate named HttpClients with token assigned?

Related

OpenIddict in abp framework - deploy in containerized environment (k8s)

abp version 6.0, tiered (.Web, .HttpApi.Host, .AuthServer), MVC.
The following messages appear in .AuthServer.
Client validation failed because 'https://webpage_url/signin-oidc' was not a valid redirect_uri for AppName_Web.
The authorization request was rejected because the redirect_uri was invalid: 'https://webpage_url/signin-oidc'.
How to properly set appsettings.json in .Web, .HttpApi.Host, .Web and .DbMigrator projects for deployment into containerized environments?
Where should be set internal (k8s) url address for auth server and when outer url (which is accessible via internet).
Url https://webpage_url/signin-oidc in the log is outer address (which is accessible via internet).
changing appsettings.json
Client validation failed because 'https://webpage_url/signin-oidc' was
not a valid redirect_uri for AppName_Web. The authorization request
was rejected because the redirect_uri was invalid:
'https://webpage_url/signin-oidc'.
Probably your redirect uri is not seeded, You can check your database if the redirect uri is added correctly for that client (application).
How to properly set appsettings.json in .Web, .HttpApi.Host, .Web and
.DbMigrator projects for deployment into containerized environments?
Where should be set internal (k8s) url address for auth server and
when outer url (which is accessible via internet).
Url https://webpage_url/signin-oidc in the log is outer address (which
is accessible via internet).
You don't change the redirect uri based on your deployment environment. It should point to a valid reachable endpoint that the openid-provider redirects to after signin.
As far as I understand, apart from normal login flow, you are having problems when interacting to openid-provider in isolated network (k8s, docker).
Since you have auth-server on real domain endpoint (like https://my-authserver.com), you are getting error from containers that tries to reach to domain (https://my-authserver.com/.well-known/openid-configuration) and you receive SSL error or not found error.
So you want internal requests done to the internal container (like http://my-auth-container/.well-known/openid-configuration) while user interacted login/logout should be done using the public domain name (https://my-authserver.com).
Instead of changing the public issuer, you can add OpenIdConnectOptions based on your deployment to configure MetadataAddress as:
context.Services.Configure<OpenIdConnectOptions>("oidc", options =>
{
options.MetadataAddress = configuration["AuthServer:MetaAddress"].EnsureEndsWith('/') +
".well-known/openid-configuration";
var previousOnRedirectToIdentityProvider = options.Events.OnRedirectToIdentityProvider;
options.Events.OnRedirectToIdentityProvider = async ctx =>
{
// Intercept the redirection so the browser navigates to the right URL in your host
ctx.ProtocolMessage.IssuerAddress = configuration["AuthServer:Authority"].EnsureEndsWith('/') +
"connect/authorize";
if (previousOnRedirectToIdentityProvider != null)
{
await previousOnRedirectToIdentityProvider(ctx);
}
};
var previousOnRedirectToIdentityProviderForSignOut =
options.Events.OnRedirectToIdentityProviderForSignOut;
options.Events.OnRedirectToIdentityProviderForSignOut = async ctx =>
{
// Intercept the redirection for signout so the browser navigates to the right URL in your host
ctx.ProtocolMessage.IssuerAddress = configuration["AuthServer:Authority"].EnsureEndsWith('/') +
"connect/endsession";
if (previousOnRedirectToIdentityProviderForSignOut != null)
{
await previousOnRedirectToIdentityProviderForSignOut(ctx);
}
};
This way, login/logout requests will be redirected to the configuration["AuthServer:Authority"] which should be a public domain (like https://my-authserver.com) and the internal requests will be redirected to the configuration["AuthServer:MetaAddress"] which should be an internal service (like http://my-auth-container)
For more details, check out:
eShopOnAbp Public-Web application configuration
eShopOnAbp Azure deployment configuration

Keycloak authentication: how can a external user get an token without exposing client secret

I have a query about how keycloak is supposed to be working with client without GUI access.
Basically I have:
A keycloak server configured with a realm, clients(Access type confidential) and Users
A server application with a GUI that also provide API, secure with keycloak (client, user, blablabla)
This is kind of working already as I am able to log on the GUI, have the redirect, etc..
Even accessing the APIs works well, when I have access to a GUI: I log on my UI, follow the redirect and get my UI to display the token. The the human (to differentiate the user from an application), can use the token in any API client.
In this context the user never sees the client secret, which is instinctively the right way. (note that I am very opened to people telling me my instinct is wrong!)
What I am NOT able to do so far is to find the way a server application (without GUI) can get a valid token?
The authorization_endpoint, as far as I understand it, requires both the client id and the client secret) to get a token, which I would rather avoid: I don't think giving my client secret to all my "customers" is the proper way to do it.
Alternatively I could create an API on my client that woudl ask for user credential and ask for the token in its behalf, but that would expose the clients credentials to my application, which is against the whole concept!
I tried setting my client Access type as public, but when I use the API call below I also get a error:
POST /auth/realms/realmname/protocol/openid-connect/tokenAPI
'grant_type=client_credentials'
'client_id=client_id'
'username=username'
'password=password'
{
"error": "unauthorized_client",
"error_description": "Public client not allowed to retrieve service account"
}
Would anyone know how this is supposed to be done ?
Thanks in advance.
Max
(...) A server application (without GUI) can get a valid token... typically using the Client Credentials flow.
But we would define in this case a dedicated Client for your server (client?) application to authenticate against. The returned token (not bound to a specific user) will serve for authorizations on allowed applications (i.e. your classic GUI or API clients).
So, basically you should (in very short):
define a specific confidential Client in your Keycloak
add the desired applications (or other Clients) to the Client Scope(s). Those you want to authorize transitively from this Client.
authenticate against this Client with Client Credentials flow (given the token endpoint, client id, credentials, scope)
ensure that you are authenticating through TLS and that parameters are included in request body (and not in headers - for enhanced privacy)
further harden security of your Client(s)
When you do not want anymore this particular server (client?) application to access your applications, you can change the corresponding "authentication" Client's secret/credentials or simply delete it.
"I don't think giving my client secret to all my "customers" is the proper way to do it."
You are right and the proposed method above strictly avoids that. Each customer would have its own credentials.
EDIT
(adding more details)
By performing as above, you would end up with the following scheme:
Flow Keycloak Server
C/S app. or Customer X <--- Client Creds ---> Auth. Client X
--- Access Token ---> Appl. Client <--> Appl. Server
C/S app. or Customer Y <--- Client Creds ---> Auth. Client Y
--- Access Token ---> Appl. Client <--> Appl. Server
Browser users <--- Standard ------> Appl. Client <--> Appl. Server
Note: this is not a detailed flow chart. Arrows mostly show relationships here.
Finally, please note that the terminology may differ a little here, but the proposed method is basically the same that Google uses. So you may aswell take some inpiration from there:
https://developers.google.com/identity/protocols/oauth2
I just had the same problem some weeks ago
In my case, I have a backend API and a frontend application that the users can use.
Eventually, I can't share the client_secret to the frontend application.
So here is my solution:
On keycloak, create a client (ex front_end_client) with grant type public
This client is going to be used by the frontend application to authenticate users using implicit flow (with PKCE will be more secure)
On keycloak, create a second client (On the same REALM as the first client) with grant type confidential, this client is going to be used by the backend API
Now, this is how it works:
Frontend app authenticate users and get the access token (Using the font_end_client)
The frontend app sends this token for every request to the backend
Backend app verify this token, and can retrieve permissions from it

RESTFul Client in Jersey is generating a new shiro (for authentication) session each time

I have a rest api that has a login resource for authentication using shiro.
When calling the login from postMan client for example then trying to perform an action, I monitor the shiro session on rest server side and it's always the same.
In addition I have a java client that uses Jersey for creating HTTP requests, but when running it I noticed that each call has a different shiro session!
Creating the client once when doing login:
Client client = ClientBuilder.newClient();
baseTarget = client.target(baseUrl);
Then in each call I have something like:
Invocation.Builder builder;
builder = baseTarget.path(apiPath + "blabla")
.queryParam("bla", bla)
.
.
.
.request(MediaType.APPLICATION_JSON);
Response response = builder.post(Entity.json(null));
How can I assure having the same session in each call?!
You will need to send include the associated session cookie (by default JSESSIONID).

Identity Server 4 issued JWT Validation failure

I have an Identity Server running based on IdentityServer 4 (.Net Core v2) targeting the full .Net framework, and I have an ASP.NET WebAPI built against ASP.Net Web API 2 (i.e. NOT .Net Core) that is using the Identity Server 3 OWIN middleware for token authentication.
When running locally, everything works just fine - I can use Postman to request an Access Token from the Identity Server using a RO Password flow, and I can then make a request to the WebAPI sending the token as a Bearer token - all works fine.
Now, when everything is hosted on our test servers, I get a problem when calling the WebAPI - I simply get an Unauthorized response. The token returned from the Identity server is ok (checked using http://jwt.io), but validation of the JWT is failing in the WebAPI.
On further investigation, after adding Katana logging, I see that a SecurityTokenInvalidAudienceException is being reported.
Audience validation failed. Audiences:
'https://11.22.33.44:1234/resources, XXXWebApi'. Did not match:
validationParameters.ValidAudience: 'https://localhost:1234/resources'
or validationParameters.ValidAudiences: 'null'
Looking at the JWT audience, we have:
aud: "https://11.22.33.44:1234/resources", "XXXWebApi"
In the WebAPI Startup, I have the call to
app.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions
{
Authority = , // path to our local ID Server
ClientId = "XXXWebApi",
ClientSecret = "XXX_xxx-xxx-xxx-xxx",
RequiredScopes = new[] { "XXXWebApi" }
});
So the JWT audience looks ok, but its obviously not matching with what is supplied by the middleware (built from the IdP discovery end point). I would have thought that because I am specifying the RequiredScopes to include XXXWebApi that would have been enough to match the JWTs audience but that seems to be ignored.
I'm unsure what to change in the WebAPI authentication options to make this work.
EDIT: I changed the WebAPI Token auth options to use the validation endpoint, and this also fails in the IdentityServer with the same error.
If I call the Identity Server introspection endpoint directly from Postman with the same token though, it succeeds.
Ok, so after a lot of head scratching and trying various things out I at least have something working.
I had to ensure the Identity Server was hosted against a publicly available DNS, and configure the Authority value in the IdentityServerBearerTokenAuthenticationOptions to use the same value.
That way, any tokens issued have the xx.yy.zz full domain name in the JWT audience (aud), and when the OWIN validation middleware in the WebAPI verifies the JWT it uses the same address for comparison rather than localhost.
I'm still slightly confused why the middleware cant just use the scope value for validation because the token was issued with the API resource scope (XXXWebAPi) in the audience, and the API is requesting the same scope id/name in the options as shown.
As far as I understand your WebAPI project is used as an API resource.
If so - remove the 'clientId' and 'clientSecret' from the UseIdentityServerBearerTokenAuthentication, keep the 'RequiredScopes' and the authority (you may also need to set ValidationMode = ValidationMode.Both).
You need them, when you are using reference tokens. From what you've said - you are using a JWT one. Check here, here and here.

Authenticating in Azure but calling web api on localhost - 401

I have an Azure mobile app authenticating on azure with FB and Twitter. I am able to login to both but when testing web api calls locally (hosted in IIS) - any controller with the Authorize attribute - returns a 401.
The alternateLoginHost is set to the Azure host and it is providing me with a token after a successful login.
I followed the instructions in the docs about setting up Local debugging with authentication.
It is using attribute based routing - config.MapHttpAttributeRoutes()
Here is my full OWIN startup class
public class OwinStartup
{
public void Configuration(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
config.MapHttpAttributeRoutes();
new MobileAppConfiguration()
.ApplyTo(config);
app.UseAppServiceAuthentication(new AppServiceAuthenticationOptions()
{
SigningKey = ConfigurationManager.AppSettings["authSigningKey"],
ValidAudiences = new[] { ConfigurationManager.AppSettings["authAudience"] },
ValidIssuers = new[] { ConfigurationManager.AppSettings["authIssuer"] },
TokenHandler = config.GetAppServiceTokenHandler()
});
app.UseWebApi(config);
}
I have seen references in the documentation about either using the global http config passed in from asp.net or creating a new one - I tried referencing the GlobalConfiguration.Configuration instead of creating a new instance but it didn't help.
Is there a way to hook into the OWIN pipeline to see if the token is making it to the app service authentication module?
[EDIT] ZUMO AUTH HEADER
I am also adding the x-zumo-auth header and setting it's value to the token from the current user like this
request.headers.append('X-ZUMO-AUTH',
app.azureClient.currentUser.mobileServiceAuthenticationToken)
And I can see it in each request being made to the api. A call to the endpoint https://mysite.azurewebsites.net/.auth/me using this mechanism is returning a correct response so I know the token is valid.
[EDIT] Verified working in Azure after adding ZUMO Version Header
deployed the web api to azure and tried to call my web api endpoint and received a bad request error. It turns out it required an additional header for the version
'ZUMO-API-VERSION', '2.0.0'
I added the header and it worked.
Tried the local call again with the version header but still getting 401.
All I can assume is the OWIN middleware is not receiving the token probably due to a config problem - need some transparency into the pipeline to test some of these theories - not sure the best way to do that
Not sure where to look next in solving this.
[EDIT] OWIN Pipeline Authenticate stage Hook
OK - figured how to set up the equivalent to asp.net pipeline event handlers in OWIN - in my startup file I added this code to execute when the Authenticate stage is reached
app.Use((context, next) =>
{
return next.Invoke();
});
app.UseStageMarker(PipelineStage.Authenticate);
Added a breakpoint and when the debugger stops I can see that in the call stack
that the Azure App Service Middleware in in the pipeline
Microsoft.Owin.Security.Infrastructure.AuthenticationMiddleware
<Microsoft.Azure.Mobile.Server.Authentication.AppServiceAuthenticationOptions>
.Invoke(Microsoft.Owin.IOwinContext context)
Confirmed OWINContext.Request.Headers Contains the Correct Auth Token
Looking in the debugger the token is indeed in the Context.Request.Headers collection at the Authenticate stage of the OWIN pipeline and it has the correct value. For some reason still receiving the 401 status
I doubly checked the value I am using for the SigningKey - copied from the WEBSITE_AUTH_SIGNING_KEY value in Azure.
[EDIT] Debugging with source and exception being thrown in ValidateIdentity
AppServiceAuthenticationHandler.ValidateIdentity
Downloaded source v1.1.157.1, loaded symbols from symbolsource but those are marked as 1.0.157.1 and visual studio is complaining that the source is different than when the module was built. Because of this I cannot step through the code to see the exception that is being caught.
[EDIT]Built and Referenced v1.1.157.1 DLL - exception type now visible
In the ValidateIdentity method when it calls
options.TokenHandler.TryValidateLoginToken
The following exception is thrown
"Could not load type 'System.IdentityModel.Tokens.JwtSecurityToken'
from assembly 'System.IdentityModel.Tokens.Jwt, Version=5.0.0.127,
Culture=neutral, PublicKeyToken=31bf3856ad364e35'."
[EDIT] Finally found and fixed the issue
There was an issue posted on GitHub for IdentityServer having to do with a breaking change with a nuget package - going from v4 -> v5 of System.IdentityModel.TokensJwt package.
https://github.com/IdentityServer/IdentityServer3/issues/3017
I downgraded my install to v4.0.x and it fixed the exception and also fixed the 401 errors I was seeing when running locally.
All is good now
Do you have the correct app settings for authSigningKey, authAudience, and authIssuer in your web.config?
FYI -- a good writeup of the setup is here in case you haven't stumbled upon it yet: http://www.systemsabuse.com/2015/12/04/local-debugging-with-user-authentication-of-an-azure-mobile-app-service/
Is your localhost serving https traffic?
If a controller is decorated with [Authorize] it will require that the request/response occurs over https.
If you're targeting http://localhost, [Authorize] will cause a 302 to https://localhost.
This 302 can break the auth process. The solution would be to serve https locally, point your client to the appropriate port (my box uses :44300), and try it out again.
Also, make sure any relevant redirect URLs go to the https:// version of the URLs you're using.