SignalR and OpenId Connect - asp.net-web-api

I have a server which uses ASP.NET Core Web Api and OpenIddict as authorization framework. Now I've added an SignalR host and want to add authorisation to it.
From different sources I found that SignalR (JS Client) wants that you send the access token in the querystring or by cookie as websockets don't support headers.
As the authentication middleware doesn't check the querystring or cookie container for an authorization entry I need to implement such an provider/retriever/resolver which reads this value by myself.
I've found a solution for IdentityServer but nothing about OpenIddict.
Where/How do I implement such an token resolver with OpenIddict?

If you use JwtBearerAuthentication then you can use OnMessageReceived to set token:
Events = new JwtBearerEvents()
{
OnMessageReceived = async (ctx) =>
{
ctx.Token = ctx.Request.Query["<qs-name>"];
}
}
Or if you use IdentityServerAuthentication then you can use TokenRetriever(not tested but it should be something like this):
TokenRetriever = (ctx) =>
{
return ctx.Request.Query["<qs-name>"];
}

Just like #adem-caglin mentioned, in IdentityserverAuthentication you use TokenRetriever and can go with the built-in functions if what you're after is the standard bearer header or a query string
TokenRetriever = (request) =>
{
// by default calls TokenRetrieval.FromAuthorizationHeader()(request);
// check if request is to signalr endpoint and only then apply FromQueryString
return TokenRetrieval.FromQueryString()(request);
}

Related

Problem in refreshing the login token from client to identity server

I have setup an Identity Server 4 on a .Net 6 web app. My web UI is another web app that is configured as the client of the Identity Sever. User is correctly refered to the login page when request accessing to a secured page/api and login is done OK. The solution also has other microservices that are also configured to use IS as oidc. The problem is after a while if I do not refresh the page, authentication fails when calling webapis. When I check the request, before the main call to the webapi controller, a request to the IS is made but is refused with CORS exception. I have configured the IS web app to accept CORS like this:
builder.Services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader());
});
and then:
app.UseCors("CorsPolicy");
What I am missing?
The mentioned settings did not solve the problem
The problem is might be caused from the expiration of the cookie or token (I'm not sure). But you should also add
builder.Services.AddSingleton<ICorsPolicyService>((container) => {
var logger = container.GetRequiredService<ILogger<DefaultCorsPolicyService>>();
return new DefaultCorsPolicyService(logger)
{
AllowedOrigins = { /*webUIOriginHere!NotUrl!*/ }
};
});
to the program.cs of Identity Server webapp and the problem should be solved.
Also adding AnyOrigin is dangerous. try doing something like this:
builder.Services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder.WithOrigins( webUIOrigin )
.AllowAnyMethod()
.AllowAnyHeader());
});

How to authenticate inside C# Web API with Google Authentication?

I am developing a C# Web Api (.NET Framework) and would like to use in parallel the AAD authentication (already working correctly) and Google Authentication. This means, my clients (javascript or just Postman) should fetch the token, include it in the Authorization header (Bearer token) and be able to execute the API methods.
However, it seems that the token I am generating with Postman is not accepted by my C# Web Api. I am always getting a HTTP 401. The AAD (Windows Azure Active Directory) works seamlessly.
Using a C# Web Api (.Net Framework 4.6.1), including Microsoft.Owin.Security.Google Nuget package v.4.0.1.
I have created the Oauth client on the google developer console, got my client ID and client secret. Also I have set up the redirect URI there.
I am using Postman Oauth2 authorization, setting following parameters:
Grant type: implicit
Callback URL: my url
AUth URL: https://accounts.google.com/o/oauth2/v2/auth
Client ID: 65xxxxxmyclientid.apps.googleusercontent.com
scope: openid email profile
Then I can log in with my google account, give consent to use the scopes, and am getting the token like this:
Access Token
ya29.Gls7....
This token is then sent as Authorization header like "Bearer ya29.Gls7...."
Startup.Auth
public void ConfigureAuth(IAppBuilder app)
{
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Tenant = ConfigurationManager.AppSettings["ida:Tenant"],
TokenValidationParameters = new TokenValidationParameters
{
ValidAudience = ConfigurationManager.AppSettings["ida:Audience"]
},
});
app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions()
{
ClientId = ConfigurationManager.AppSettings["GoogleClientId"],
ClientSecret = ConfigurationManager.AppSettings["GoogleClientSecret"],
});
}
ValuesController
public class ValuesController : ApiController
{
[Authorize]
// GET api/values/5
public string Get(int id)
{
return "value";
}
}
Every time I call the API using Postman, I am getting a 401, even though the bearer token is included in the request authorization header.
HTTP GET https://localhost:44385/api/values/1
Edit: There is no custom code from my side which would validate the token. I assumed that is being done automatically by the library. In fact, this is done automatically for AAD tokens, but not in the case of Google Oauth.
I expect to get inside the code of the controller method, having at least the identity name set with the google account name/email.
What am I missing to be authenticated inside my controller method with google account?

Asp.net Core Client with Identityserver 3 missing claims

Given:
A JavaScript app that authenticates with oidc over identityerver v3
A Asp.net Core Webapi that authenticates with the given bearer token to identiyserver
The Javascript clients makes calls with the access token itself to the api.
Problem:
The Authentication suceeds but the restored principal is missing some custom claim like "username" and "familyName". I can see that the oidc client in the javascript client has these informations
some claims like "idp" is set in both Javascript and Api Client. But bot are not handled explicitly.
The main difference is that idp is part of the access_token which the username is not.
the configuration of the api is :
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
var authority = config["identity:authority:url"];
app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
{
LegacyAudienceValidation = true,
Authority = authority,
RequireHttpsMetadata = false,
EnableCaching = false,
ApiName = "MyApp.Read",
});
Any hint what i'm missing ? ( I assume it is some kind of profile read in the api?)
Workaround
I extend the configuration with JwtBearerEvents and make a manual read with userclient when the token was authenticated like this
JwtBearerEvents = new JwtBearerEvents
{
OnTokenValidated = async context =>
{
string header = context.Request.Headers["Authorization"];
string accessToken = header.Substring(6);
var result = await userInfoClient.GetAsync(accessToken);
but is this the intended way? Are extended / profile claims meant to be returned only by manually querying them?

google ExchangeCodeForTokenAsync invalid_grant in webapi

i have implemented GoogleAuthorizationCodeFlow scenario from google api client dotnet and tutorial to get token from what my client sent to server as a code. but when i call flow.ExchangeCodeForTokenAsync , I get the following error :
{"Error:\"invalid_grant\", Description:\"\", Uri:\"\""}
I read google authorization invalid_grant and gusclass oauth 2 using google dotnet api client libraries but they didn't help me and. I think it must be very simple but I don't know why it doesn't work.
For client side , I have used Satellizer and this is my server Codes:
public bool PostExchangeAccessToken(GoogleClientAccessCode code)
{
string[] SCOPES = { "email" };
IAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = new ClientSecrets()
{
ClientSecret = "******",
ClientId = "********.apps.googleusercontent.com"
},
Scopes = SCOPES
});
try
{
TokenResponse token;
token = flow.ExchangeCodeForTokenAsync("*****#gmail.com", Newtonsoft.Json.JsonConvert.SerializeObject(code), "https://localhost:44301/",
CancellationToken.None).Result;
}
catch (Exception ex)
{
throw ex;
}
return true;
}
what is the problem?
On Github I found that I must use the Token from the client and use
GoogleAuthorizationCodeFlow.Initializer()
to create my UserCredential object.
You can check your google developer console settings.(Authorized redirect URIs)
Credentials => OAuth 2.0 client IDs => Your Application Settings => Authorized redirect URIs
You must add url. ("https://localhost:44301/")
My code :
flow.ExchangeCodeForTokenAsync("me", authCode, redirectUri, CancellationToken.None).Result;
Authorized redirect URIs
For use with requests from a web server. This is the path in your application that users are redirected to after they have authenticated with Google. The path will be appended with the authorization code for access. Must have a protocol. Cannot contain URL fragments or relative paths. Cannot be a public IP address.

SailsJS - using sails.io.js with JWT

I have implemented an AngularJS app, communicating with Sails backend through websockets, using sails.io.js.
Since the backend is basically a pure API and will be connected to from other apps as well, I'm trying to disable sessions completely and use JWT.
I have set up express-jwt and can use regular HTTP requests quite nicely, but when I send a request through sails.io.js, nothing happens at all - websocket request keeps pending on the client, and there's nothing happening on the server (with "silly" log level).
I've tried patching sails.io.js to support the query parameter, and when connecting, I send the token from Angular, but in the best case, I get a response with error message coming from express-jwt saying credentials are missing...
I've also seen some hints that socket.js in sails needs to be modified with beforeConnect, I've seen socketio-jwt, but have no idea where and how to plug that in, in Sails.
Has anyone implemented this and is using JWT with Sails and sockets? I'd appreciate any kind of hint in what direction to go :)
I realised that policy I've put in place and that was using express-jwt abstracted too much away from me, so I didn't figure out what exactly was happening. Once I looked at other examples, I've figured out that I only needed to check what's different for websocket requests than regular, and I quickly found a way around the problem.
So:
set up token signing and sending on login
Angular takes the token and saves to local storage
Create an interceptor for HTTP requests to add authorization header and token
Fix up sails.io.js to forward query parameters provided through options (as mentioned in the question)
When connecting using sails.io.js, send token as query parameter, i.e. url + '?token=' + token
In sails policy, check all combinations for token, including req.socket.handshake.query, as below:
module.exports = function (req, res, next) {
var token;
if (req.headers && req.headers.authorization) {
var parts = req.headers.authorization.split(' ');
if (parts.length == 2) {
var scheme = parts[0],
credentials = parts[1];
if (/^Bearer$/i.test(scheme)) {
token = credentials;
}
} else {
return res.json(401, {err: 'Format is Authorization: Bearer [token]'});
}
} else if (req.param('token')) {
token = req.param('token');
// We delete the token from param to not mess with blueprints
delete req.query.token;
}
// If connection from socket
else if (req.socket && req.socket.handshake && req.socket.handshake.query && req.socket.handshake.query.token) {
token = req.socket.handshake.query.token;
} else {
sails.log(req.socket.handshake);
return res.json(401, {err: 'No Authorization header was found'});
}
JWTService.verifyToken(token, function (err, token) {
if (err) {
return res.json(401, {err: 'The token is not valid'});
}
sails.log('Token valid');
req.token = token;
return next();
});
};
It works well! :)

Resources