SignalR Core JS Client not passing Authorisation headrs on "Upgrade " Request - asp.net-core

I Have to implement a push notification service using ASP .Net Core. as obvious choice is to use SignalR Core.
Our platform setup is using Azure App gateway and it is configured to not allow unauthenticated requests.
We have setup WebSockets communication with SignalR.
Under the hood , SignalR Core follows these steps:
POS ../negociate -> OK with hub_token and supported transport
GET (sends Upgrade header and WebSockets token)../Hub?id={hub_token} -? fail
when investigating why the step 2 does not upgrade the connection to a WebSocket connection , I have noticed that the GET request is missing Authorization header. Obviously AG block this request and doesn't even get to the API.
I have tried manually to make a "handshake" with postman.
the above steps :
OK
included Authorization JWT header -> result 101 ,and Fiddler confirm the connection is opened.
I have researched the documentation and found that Authorization headers are not supported.
did anyone tried any workaround ? hen is the next release of the #aspnet/signalr client?

Did you specified the accessTokenFactory?
let connection = new signalR.HubConnectionBuilder()
.withUrl("/myhub", {
accessTokenFactory: () => {
// Get and return the access token.
// This function can return a JavaScript Promise if asynchronous
// logic is required to retrieve the access token.
}
})
.build();
More info here.

so the final resolution is:
that in browsers is a limitation for sending JWT header along with HTTP 101 UPGRADE

Related

Identity Server 4, External providers and Web API

I’m in process of developing system which consists from such parts:
- Some services under gateway (Ocelot)
- Mobile client (iOS)
- Identity Server 4
Mobile client hasn’t been prepared yet, so I use Postman for emulating requests from it. My problem is implementation of Authentication with External providers, like Google. It’s my first experience of using IS 4, so I have some misunderstanding and difficulties. Excuse me, if my question is too abstract or if I miss smth obvious.
I successfully deployed IS 4 using all this tutorials and it works with Password Credentials flow in a proper way: I request IS for access token, sending user credentials, it returns token and I can successfully use it for access to my API methods.
Situation with External Providers are different. I’ve overviewed this tutorial (https://learn.microsoft.com/en-us/aspnet/core/security/authentication/social/google-logins?view=aspnetcore-3.1) and some other and add code from it to the IS project. I can successfully log in with Google, using a button on that IS4 web-page which goes with IS 4 Quickstart UI template. But no chance to work with API. As I understand in such workflow client-app should go for a token not to my IS as in example with a local user, but to the Google Auth provider. And I emulated it with Postman and got a strange access_token which has no data and it_token which contains username, email and so on. I try to use this id_token with requests to my API. The result is always 401.
Where I’m wrong? How should I build requests to API with token from Google? Or I have misunderstanding and there should be another flow: client goes to IS with specific request, IS goes to Google and then returns proper token to Client?
Here is configuration of authecation on the side of Web API app:
private void ConfigAuthentication(IServiceCollection services)
{
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
{
options.Authority = "http://localhost:5000";
options.RequireHttpsMetadata = false;
options.Audience = "k_smart_api";
});
}
Here is config of Google-Auth on the side of IdentityServer:
services.AddAuthentication().AddGoogle(opts => {
opts.ClientId = "My google client Id";
opts.ClientSecret = "my google client secret";
opts.SignInScheme = IdentityConstants.ExternalScheme;
opts.SaveTokens = true;
});
This is how I get Access Token:
postman exampple
The tokens you get back from Google, is only used to Authenticate the user in Identity Server. Then after Identity Server receives those tokens, it sign-in the user and create new tokens (ID+access) that are passed to your client. you should look at using the authorization code flow in your client to authenticate the user and to get the tokens. then use the access token received to access your API.
do remember that the tokens received from Google are not used to give access to your APIs.

Alternate to get access token from challenge handler response WL.Client.getLastAccessToken

I am trying to use the accessToken from a custom challenge handler. We want to send that accessToken and token id to an external service. that will validate the token by calling mobile first token validation endpoint.
How to read access token and token ID. In MFP 6.3 we have WL.Client.getLastAccessToken that has been deprecated. Is there any API which read the access token received for a challenge handler.
obtainAccessToken
obtainAccessToken(scope, onSuccess, onFailure)
Obtains an OAuth 2.0 access token from the Worklight server. The token is required in order to send a request to an external server which uses this Worklight authentication method.
Parameters:
scope - Optional. The name of the security test protecting the external resource. If the scope is null, a token for the application's default security test will be obtained.
onSuccess - The success callback. Note that there is no need to parse the response. Instead, use WL.Client.getLastAccessToken(scope) in order to get the last obtained token.
onFailure - The failure callback.
Deprecated:
Since version 7.0
Is there any alternate API in MobileFirst 7.1
Response Data: {"access_token":"eyJqcGsiOnsibW9kIjoiQU0wRGQ3eEFkdjZILXlnTDdyOHFDTGRFLTNJMmtrNDV6Z1p0RGRfcXM4ZnZuWWZkaXFUU1Y0XzJ0Nk9HRzhDVjVDZTQxUE1wSXdtTDQxMFg5SVpudmh4b1lpRmNNU2FPZUlxb2UtckpBMHVadXcyckhoWFozV1ZDZUtlelJWY0NPWXNRTi1tUUswbWZ6NV8zby1ldjBVWXdYa1NPd0JCbDFFaHFJd1ZEd09pZWcySk1HbDBFWHNQWmZrTlpJLUhVNG9NaWktVHJOTHpSV2tNbUx2bTA5aEw1em9zVU5BMTV2ZUNLcGgyV3BtU20yUzYxbkRoSDdnTEVveW1EblRFalBZNUFvaDJpbkktMzZSR1lWTVVVYk80NkNyTlVZdUlvYk9pWGxMekJJaHVJQ3BmVmR4VF94N3N0S1g1QzlCZk1UQjRHa09IUDVjVXY3TnoxZERoSVB1OCIsImV4cCI6IkFRQUIiLCJhbGciOiJSU0EifSwiYWxnIjoiUlMyNTYifQ.eyJpbWYuc2NvcGUiOnsid2xfYW50aVhTUkZSZWFsbSI6eyJleHAiOjE0OTYyNTk0NzEsIm1hbmRhdG9yeSI6dHJ1ZX0sIkN1c3RvbUF1dGhlbnRpY2F0b3JSZWFsbSI6eyJleHAiOjE0OTYyNTk0NzF9LCJ3bF9kaXJlY3RVcGRhdGVSZWFsbSI6eyJleHAiOjE0OTYyNTk0NzAsIm1hbmRhdG9yeSI6dHJ1ZX0sIndsX3JlbW90ZURpc2FibGVSZWFsbSI6eyJleHAiOjE0OTYyNTYxNzAsIm1hbmRhdG9yeSI6dHJ1ZX0sIndsX2RldmljZU5vUHJvdmlzaW9uaW5nUmVhbG0iOnsiZXhwIjoxNDk2MjU5NDcxLCJtYW5kYXRvcnkiOnRydWV9LCJ3bF9hbm9ueW1vdXNVc2VyUmVhbG0iOnsiZXhwIjoxNDk2MjU5NDcwLCJtYW5kYXRvcnkiOnRydWV9fSwiaXNzIjoiaHR0cDpcL1wvcHJhc2FubmFzLW1icC5tZXRsaWZlLmNvbToxMDA4MFwvQ3VzdG9tQXV0aFwvYXV0aG9yaXphdGlvblwvIiwiZXhwIjoxNDk2MjU5NDcxLCJwcm4iOiI0ODE4YTJkODNkMDE0ZDkxNzcwNTdmOTc3MGI1OThkOTA5NmUxZmI3In0.qiej5KRanOOx_sjdCgdl8cFVLC88_V1yIcAML2GetHEjH8-ORyabJ0Ax0kjgEU5tMgEh6wg7A0LeW9rcZR_A9K5biLEQRJBvqcjKzedmq9DFYB_GluRL0W7pDyOk2ZpRBKQm5FXsxjKKda-yVd_7i7ipfYjMqUWctcloeXelDkPxbfxVxB1X7lyqmihflvE3ir9UG7WkxxbEAtZIUIdMkmmwCoitlTTjKPLjHfihlEJQQEm59jgXuBv2l62nWVG9eoqNnTCuYH3PlpiDK1YyK0ThBZxjAa2pLkOH1myPQRH2e-rfwJlaOit5ZosEm7pwXM2AEnvKNYyhw_2RPAnLVA","scope":"wl_antiXSRFRealm CustomAuthenticatorRealm wl_directUpdateRealm wl_remoteDisableRealm wl_deviceNoProvisioningRealm wl_anonymousUserRealm","id_token":"eyJqcGsiOnsibW9kIjoiQU0wRGQ3eEFkdjZILXlnTDdyOHFDTGRFLTNJMmtrNDV6Z1p0RGRfcXM4ZnZuWWZkaXFUU1Y0XzJ0Nk9HRzhDVjVDZTQxUE1wSXdtTDQxMFg5SVpudmh4b1lpRmNNU2FPZUlxb2UtckpBMHVadXcyckhoWFozV1ZDZUtlelJWY0NPWXNRTi1tUUswbWZ6NV8zby1ldjBVWXdYa1NPd0JCbDFFaHFJd1ZEd09pZWcySk1HbDBFWHNQWmZrTlpJLUhVNG9NaWktVHJOTHpSV2tNbUx2bTA5aEw1em9zVU5BMTV2ZUNLcGgyV3BtU20yUzYxbkRoSDdnTEVveW1EblRFalBZNUFvaDJpbkktMzZSR1lWTVVVYk80NkNyTlVZdUlvYk9pWGxMekJJaHVJQ3BmVmR4VF94N3N0S1g1QzlCZk1UQjRHa09IUDVjVXY3TnoxZERoSVB1OCIsImV4cCI6IkFRQUIiLCJhbGciOiJSU0EifSwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiJkZW1vOkN1c3RvbUF1dGhlbnRpY2F0b3JSZWFsbTo0ODE4YTJkODNkMDE0ZDkxNzcwNTdmOTc3MGI1OThkOTA5NmUxZmI3IiwiaW1mLmFuYWx5dGljcyI6eyJlbnZpcm9ubWVudCI6ImlwaG9uZSIsImlkIjoiSHlicmlkQ3VzdG9tQXV0aCIsInZlcnNpb24iOiIxLjAifSwiaW1mLmFwcGxpY2F0aW9uIjp7ImVudmlyb25tZW50IjoiaXBob25lIiwiaWQiOiJIeWJyaWRDdXN0b21BdXRoIiwidmVyc2lvbiI6IjEuMCJ9LCJpbWYuZGV2aWNlIjp7Im9zVmVyc2lvbiI6IjkuMyIsIm1vZGVsIjoiaVBob25lIiwiaWQiOiJCRUU5QjcwMC00NzkwLTRDQzEtOENCOC1GQUExREQwMjk4OTMiLCJwbGF0Zm9ybSI6ImlPUyJ9LCJpbWYudXNlciI6eyJhdXRoQnkiOiJDdXN0b21BdXRoZW50aWNhdG9yUmVhbG0iLCJhdHRyaWJ1dGVzIjoie1wiQXV0aGVudGljYXRpb25EYXRlXCI6XCJNYXkgMzEsIDIwMTcgMjozNzo1MSBQTVwifSIsImlkIjoiZGVtbyJ9LCJpc3MiOiJodHRwOlwvXC9wcmFzYW5uYXMtbWJwLm1ldGxpZmUuY29tOjEwMDgwXC9DdXN0b21BdXRoXC9hdXRob3JpemF0aW9uXC8iLCJleHAiOjE0OTYyNTk0NzEsImlhdCI6MTQ5NjI1NTg3MX0.ojcboxmS-7pQqTx1ebugTuUalbO61_HGmpVSi6_XbIQ_VCknq1zAjRgMeU0NWeaKPHDZiZlEr-nst9EUKzlKvdQNJfDZ-jjF-Nw1IVV0bfy5_YfrDWju3gY5gzNmxi188SydKxDVf6Ncv02YA6SpNU2XaoArrfGnkhmjbEaZUaWj9e8SF9xbMmaNgDfKOQs2WFJZfRY2DdkG1YodVPipxbtD81Bw4rZ-vroAkXZznd9meWmTZx9bxKtNcMqtala8NFvR6hHiLfKZuZno7FjVv26UV8pfHuLed24fHuMAxhD2wLCKfo6ZDarrcWRVZb30tJZ0AD4mGhnV6Y5PxFO7ng","token_type":"bearer","expires_in":3600}
For migrating from 6.3 to 7.1,
Please refer to ibm.com/support/knowledgecenter/SSHS8R_7.1.0/… , I see that you can use WLauthorizationManager.getCachedAuthorizationHeader() available in 7.1
from 7.1 to 8.0 ,
Please refer here Alternate API's for deprecated APIs in 7.1 https://www.ibm.com/support/knowledgecenter/SSHS8R_8.0.0/com.ibm.worklight.upgrade.doc/devref/c_sdk_changes4migration.html
In this particular scenario, https://www.ibm.com/support/knowledgecenter/SSHS8R_8.0.0/com.ibm.worklight.apiref.doc/html/refjava-worklight-android-native/html/com/worklight/wlclient/api/WLAuthorizationManager.html has to be used.
Have you tried WLAuthorizationManager.obtainAccessToken(scope) ?

ASP.NET Core Openiddict throws "An OpenID Connect response cannot be returned from this endpoint"

I follow instruction in openiddict server example using password flow from https://github.com/openiddict/openiddict-samples/tree/master/samples/PasswordFlow
but have no success.
It throws InvalidOperationException: An OpenID Connect response cannot be returned from this endpoint at route /connect/token:
return SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme);
Postman params:
Content-Type: application/x-www-form-urlencoded
Params: username=..&password=...&grantType=password&scope=offline_access+profile+email
I spent my day for researching but there is no information about cannot be returned from this endpoint exception. And many people can run openiddict example except me.
Here is apart of Startup.cs:
services.AddEntityFrameworkSqlite()
.AddDbContext<MisapayContext>(options =>
{
options.UseOpenIddict<int>();
});
//....
services.AddOpenIddict<int>()
.AddEntityFrameworkCoreStores<MisapayContext>()
.DisableHttpsRequirement()
.EnableTokenEndpoint("/connect/token")
.EnableLogoutEndpoint("/connect/logout")
.EnableUserinfoEndpoint("/connect/userinfo")
.UseJsonWebTokens()
.AllowPasswordFlow()
.AllowRefreshTokenFlow()
.AddEphemeralSigningKey();
services.AddMvc(config =>
{
config.Filters.Add(new ApiExceptionFilter());
}).AddJsonOptions(options =>
{
options.SerializerSettings.Formatting = Newtonsoft.Json.Formatting.Indented;
options.SerializerSettings.DateTimeZoneHandling = Newtonsoft.Json.DateTimeZoneHandling.Local;
});
Edited: I think problem is OpenIdConnectRequest, it can not be binded if use:
OpenIddictBuiler.AddMvcBinders()
Will throws The OpenID Connect request cannot be retrieved from the ASP.NET context.`
Otherwise, remove it, OpenIdConnectRequest in AuthorizationController can get properly. And I can get request information such as username, password grantType etc... Strange... right?
Some other information:
Asp.net Core SDK 1.1
Project.json : https://gist.github.com/trinvh/47f29468887c209716098bc4c76181a7
Startup.cs: https://gist.github.com/trinvh/75b7a12fbee754d0ea8cf251f2da9fe9
AuthorizationController.cs: https://gist.github.com/trinvh/089015b2573cae550856631e72b81374
Any help will be appreciated!
Okay, here's what's happening:
You've configured OpenIddict to use /connect/token as the token endpoint address.
The token request you send via Postman points to /connect/token/, which is actually a totally different URL (/connect/token != /connect/token/).
Since the address differs from the registered endpoint path, OpenIddict doesn't handle the request and refuses to consider it as a token request.
For some reasons, MVC accepts to handle your /connect/token/ request and invokes the Exchange action, even though the route doesn't match the requested URL.
Since you haven't registered the OpenIddict MVC binder in the MVC options, MVC uses its default binder to construct the OpenIdConnectRequest object, which allows the OpenIdConnectRequest.GrantType parameter to be resolved from the invalid grantType parameter (it wouldn't happen with the dedicated OpenIddict binder).
Your token endpoint action ends up calling SignIn to return a token response.
Under the hood, OpenIddict detects that you called SignIn outside the normal token request processing - since it didn't consider the request as a token request, due to the paths difference - and aborts this unsafe operation by throwing an InvalidOperationException.
I'll ping the MVC folks to make sure they are aware of this bug.
Edit: after some research, it looks like this behavior is "by design" and was inherited from ASP.NET MVC. I opened a feature request in the aspnet/Mvc repository to add a new way to use "strict comparison" for routes matching.

OWIN/OAuth2 3rd party login: Authentication from Client App, Authorization from Web API

I am trying to create a Web API that allows the API's clients (native mobile apps) to login using a 3rd party cloud storage provider. I'm using the following general flow from Microsoft:
Here is what I am trying to achieve:
I am using the default ASP.NET Web API Visual Studio template with external authentication, along with the OWin.Security.Providers Nuget package for Dropbox login functionality, and the existing built-in login functionality for Google (Drive) and Microsoft (OneDrive).
The issue I'm having is that the built-in functionality all seems to do the authentication and authorization as part of one flow. For example, if I set up the following in Startup.Auth.cs:
DropboxAuthenticationOptions dropboxAuthOptions = new DropboxAuthenticationOptions
{
AppKey = _dropboxAppKey,
AppSecret = _dropboxAppSecret
};
app.UseDropboxAuthentication(dropboxAuthOptions);
... and navigate to this url from my web browser:
http://<api_base_url>/api/Account/ExternalLogin?provider=Dropbox&response_type=token&client_id=self&redirect_uri=<api_base_url>
I am successfully redirected to Dropbox to login:
https://www.dropbox.com/1/oauth2/authorize?response_type=code&client_id=<id>&redirect_uri=<redirect_uri>
... and then after I grant access, am redirected back to:
http://<api_base_url>/Help#access_token=<access_token>&token_type=bearer&expires_in=1209600
... as you can see the token is part of that, so could be extracted. The problem is that the client needs to be the one navigating to Dropbox and returning the authorization code back up to the Web API, and the Web API would send the authorization code back to the third party to get the token which would then be returned to the client... as shown in the diagram above. I need the ExternalLogin action in the AccountController to somehow retrieve the Dropbox url and return that to the client (it would just be a json response), but I don't see a way to retrieve that (it just returns a ChallengeResult, and the actual Dropbox url is buried somewhere). Also, I think I need a way to separately request the token from the third party based on the authorization code.
This post seems a little similar to what I am trying to do:
Registering Web API 2 external logins from multiple API clients with OWIN Identity
... but the solution there seems to require the client to be an MVC application, which is not necessarily the case for me. I want to keep this as simple as possible on the client side, follow the flow from my diagram above, but also not reinvent the wheel (reuse as much as possible of what already exists in the OWIN/OAuth2 implementation). Ideally I don't want the client to have to reference any of the OWIN/OAuth libraries since all I really need the client to do is access an external url provided by the API (Dropbox in my example), have the user input their credentials and give permission, and send the resulting authorization code back up to the api.
Conceptually this doesn't sound that hard but I have no idea how to implement it and still use as much of the existing OAuth code as possible. Please help!
To be clear, the sample I mentioned in the link you posted CAN be used with any OAuth2 client, using any supported flow (implicit, code or custom). When communicating with your own authorization server, you can of course use the implicit flow if you want to use JS or mobile apps: you just have to build an authorization request using response_type=token and extract the access token from the URI fragment on the JS side.
http://localhost:55985/connect/authorize?client_id=myClient&redirect_uri=http%3a%2f%2flocalhost%3a56854%2f&response_type=token
For reference, here's the sample: https://github.com/aspnet-security/AspNet.Security.OpenIdConnect.Server/tree/dev/samples/Mvc/Mvc.Server
In case you'd prefer a simpler approach (that would involve no custom OAuth2 authorization server), here's another option using the OAuth2 bearer authentication middleware and implementing a custom IAuthenticationTokenProvider to manually validate the opaque token issued by Dropbox. Unlike the mentioned sample (that acts like an authorization proxy server between Dropbox and the MVC client app), the JS app is directly registered with Dropbox.
You'll have to make a request against the Dropbox profile endpoint (https://api.dropbox.com/1/account/info) with the received token to validate it and build an adequate ClaimsIdentity instance for each request received by your API. Here's a sample (but please don't use it as-is, it hasn't been tested):
public sealed class DropboxAccessTokenProvider : AuthenticationTokenProvider {
public override async Task ReceiveAsync(AuthenticationTokenReceiveContext context) {
using (var client = new HttpClient()) {
var request = new HttpRequestMessage(HttpMethod.Get, "https://api.dropbox.com/1/account/info");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", context.Token);
var response = await client.SendAsync(request);
if (response.StatusCode != HttpStatusCode.OK) {
return;
}
var payload = JObject.Parse(await response.Content.ReadAsStringAsync());
var identity = new ClaimsIdentity("Dropbox");
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, payload.Value<string>("uid")));
context.SetTicket(new AuthenticationTicket(identity, new AuthenticationProperties()));
}
}
}
You can easily plug it via the AccessTokenProvider property:
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions {
AccessTokenProvider = new DropboxAccessTokenProvider()
});
It has its own downsides: it requires caching to avoid flooding the Dropbox endpoint and is not the right way to go if you want to accept tokens issued by different providers (e.g Dropbox, Microsoft, Google, Facebook).
Not to mention that if offers a very low security level: since you can't verify the audience of the access token (i.e the party the token was issued to), you can't ensure that the access token was issued to a client application you fully trust, which allows any third party developer to use his own Dropbox tokens with your API without having to request user's consent.
This is - obviously - a major security concern and that's why you SHOULD prefer the approach used in the linked sample. You can read more about confused deputy attacks on this thread: https://stackoverflow.com/a/17439317/542757.
Good luck, and don't hesitate if you still need help.

how do you request a session from servicestack basic authentication, at /auth/basic?

I have set up a servicestack service with basic authentication using the first example, here:
https://github.com/ServiceStack/ServiceStack/wiki/Authentication-and-authorization
This automatically sets up a route: /auth/basic
However, I cannot find any information or examples on how to format a request to this URL (Variables/GET/POST/Auth Header, etc.).
I am able to access a simple service using the basic authentication credentials, so they are active and correct.
I have no custom authentication plugged in, just basic authentication.
I have tried:
Using a JsonServiceClient to send UserName and Password variables by GET or Json POST to /auth/basic, with and without an Auth header also containing the user & pass.
Using a browser to send GET requests with URL parameters of the user/pass, or as http://user:pass#localhost:123/auth/basic
I always just get "HTTP/1.1 401 Invalid BasicAuth credentials".
The only examples I can find involve some kind of custom authentication, and then /auth/credentials is accessed, but I want to use /auth/basic
I have looked at the code and it looks like it reads an Auth header, but the service does not accept one.
I am actually trying to get this working so I can then disable it and verify it is disabled (I want to require basic authentication for every request).
Questions are:
What is the correct way to call the /auth/basic service? I will take a servicestack client API example, specifications or even a raw http request!
How do you disable the /auth services altogether?
Many thanks.
What is the correct way to call the /auth/basic service? I will take a servicestack client API example, specifications or even a raw http request!
var client = new JsonServiceClient("http://localhost:56006/api");
var resp = client.Post(new Auth() { UserName = "TestUser", Password = "Password" });
This assumes you have also registered an ICacheClient and IAuthUserRepository (and added a user account)
The JSON format looks like this if you call into /auth/basic?format=json
{
"UserName": "admin",
"Password": "test"
"RememberMe": true
}
How do you disable the /auth services altogether?
Don't add the AuthFeature plugin to configuration.
You can also remove plugins
Plugins.RemoveAll(x => x is AuthFeature);
Putting the following in apphost config seems to do the trick.
//Disable most things, including SOAP support, /auth and /metadata routes
SetConfig(new EndpointHostConfig()
{
EnableFeatures = Feature.Json | Feature.Xml
});
I am a little suspicious about what this does to /auth however, because it returns an empty response, while most routes return 404.
So, would this truly disable the /auth functionality? As in, if someone formed a correct request to /auth/credentials, will it still return an empty response?