SignalR Authentication Identity is always null - signalr

I have a WPF app that I am connecting to a SignalR API. However, I am having some issues with my Identity. When I actually call an API Endpoint GET, POST, PUT, or DELETE The Identity is populated correctly (all of the claims are there). When I am connecting to SignalR the Identity Claims are not there.
This is where I register my Jwt token
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options => {
options.TokenValidationParameters = new TokenValidationParameters {
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = configuration["Jwt:Issuer"],
ValidAudience = configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["Jwt:Key"]))
};
options.Authority = configuration["Jwt:Authority"];
options.RequireHttpsMetadata = false;
options.SaveToken = true;
options.Events = new JwtBearerEvents()
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
// If the request is for our hub...
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) &&
(path.StartsWithSegments("/hubs")))
{
// Read the token out of the query string
context.Token = accessToken;
}
return Task.CompletedTask;
},
};
});
and here is where I register my connection
_connection = new HubConnectionBuilder()
.ConfigureLogging(logBuilder =>
{
logBuilder.AddConsole();
logBuilder.AddDebug();
})
.WithUrl($"{url}/hubs", options =>
{
options.AccessTokenProvider = _userService.GetToken;//returns Task.FromResult(userToken)
})
.WithAutomaticReconnect(new[]
{
TimeSpan.Zero,
TimeSpan.FromSeconds(2),
TimeSpan.FromSeconds(10),
TimeSpan.FromSeconds(30),
TimeSpan.FromSeconds(60),
TimeSpan.FromSeconds(120)
})
.Build();
My Jwt object in configuration
"jwt":{
"Key":"some random generated key",
"Issuer":"https://localhost:5001/",
"Audience":"https://localhost:5001/",
"Authority":"https://localhost:5001/"
},
Can you please explain what I am doing wrong here?

I actually found the answer to my own question.
I changed
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
to
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
});
and that did it!
Hope this helps someone else.

Related

Scheme already exists: Bearer

I'm implementing login using Microsoft provider also implement the default login using jwt
when I run the project I got this error (Scheme already exists: Bearer)
when i comment this part, project run successfully
//.AddJwtBearer(x =>
// {
// x.SaveToken = true;
// x.TokenValidationParameters = tokenValidationParameters;
// })
here is my code
var jwtSettings = new JWTSettings();
var tokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(jwtSettings.Secret)),
ValidateIssuer = false,
ValidateAudience = false,
RequireExpirationTime = false,
ValidateLifetime = true
};
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(x =>
{
x.SaveToken = true;
x.TokenValidationParameters = tokenValidationParameters;
})
.AddMicrosoftIdentityWebApi(configuration, "AzureAd")
.EnableTokenAcquisitionToCallDownstreamApi()
.AddMicrosoftGraph(configuration.GetSection("DownstreamApi"))
.AddInMemoryTokenCaches();
Scheme already exists: Bearer
It means you have more than one schemes with the same name.
You can try to set the parameter authenticationScheme into JwtBearerExtensions.AddJwtBearer Method.Here is the official doc.
And if you want to select the scheme,you can refer to the doc,and try to use:
[Authorize(AuthenticationSchemes = xxx)]

SignalR core JWT authentication not working

I'm trying to authenticate with JWT in .NET Core 2.2 with SignalR. I don't get an error message that it doesn't work on either the server or the client-side. I have set breakpoints on the server-side where the authentication is supposed to happen. Does anyone have any idea why it's not working? The JWT is built with an asymmetric signature algorithm.
This is how I generate the JWT:
var utcNow = DateTime.UtcNow;
using (RSA privateRsa = RSA.Create())
{
privateRsa.FromXmlFile(Path.Combine(HttpContext.Current.Server.MapPath("~"),
"Keys",
ConfigurationManager.AppSettings["PrivateKey"]
));
var privateKey = new RsaSecurityKey(privateRsa);
SigningCredentials signingCredentials = new SigningCredentials(privateKey, SecurityAlgorithms.RsaSha256);
var claims = new Claim[]
{
new Claim(JwtRegisteredClaimNames.Sub, user.ID.ToString()),
//new Claim(JwtRegisteredClaimNames.NameId, user.FullName),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Iat, utcNow.ToString())
};
var jwt = new JwtSecurityToken(
signingCredentials: signingCredentials,
claims: claims,
notBefore: utcNow,
expires: utcNow.AddMonths(12),
audience: "https://pacsonweb.com",
issuer: "PACSonWEB3 App"
);
return new JwtSecurityTokenHandler().WriteToken(jwt);
This is how i'm authenticating on the server side :
RsaSecurityKey signingKey = new RsaSecurityKey(publicRsa);
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(config =>
{
config.RequireHttpsMetadata = true;
config.SaveToken = true;
config.TokenValidationParameters = new TokenValidationParameters()
{
IssuerSigningKey = signingKey,
ValidateAudience = true,
ValidAudience = this.Configuration["Tokens:Audience"],
ValidateIssuer = true,
ValidIssuer = this.Configuration["Tokens:Issuer"],
ValidateLifetime = true,
ValidateIssuerSigningKey = true
};
});
This is how I send the JWT with SignalR on the client side:
hubConnection = new HubConnectionBuilder().WithUrl(hubUrl, (opts) =>
{
opts.AccessTokenProvider = () => Globals.GetJWTToken();
opts.HttpMessageHandlerFactory = (message) =>
{
if (message is HttpClientHandler clientHandler)
{
// bypass SSL certificate
clientHandler.ServerCertificateCustomValidationCallback += CheckCertificate;
clientHandler.CheckCertificateRevocationList = false;
}
return message;
};
}).Build();

Setting up OpenID Connect for .NET Core Web API

I have a simple application which uses Angular as front-end and a .NET Core Web API as back-end services. Now I want to secure my WEB API layer. I though I can use OpenID Connect for that purpose. But all the examples or documentation online uses some identity management systems to like (Keycloak, Okta) but i just want to use my user data from a SQL database.
So something like, I hit the WEB API from Angular to get the token generated(using OpenID?) based on the user details sent. The i can just use the token to Authorize users. I want to use OpenID so that i can use some other identity management systems later if i want to.
my startup class in WEB API
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(o =>
{
o.ClientId = "sa";
o.ClientSecret = "sa";
o.Authority = "https://localhost:44352";
o.GetClaimsFromUserInfoEndpoint = true;
o.RequireHttpsMetadata = true;
});
I added a controller with Authorize attribute and added a test method to see what happens when i hit that from Swagger
I see the following error
IOException: IDX20804: Unable to retrieve document from: 'https://localhost:44352/.well-known/openid-configuration'
I am not sure what the error is.
Also I would like to ask if i doing this correct. Can i use the same API (Authority? as in ( o.Authority = "https://localhost:44352";)) to authenticate/get token from).
What you'll need for OpenIDConnect is definitely a Server that implements the oidc-spec.
The .well-known/... url is part of the oidc discovery spec, which Servers like identityserver and keycloak implement. It gives a standardized List of other endpoints to retrieve tokens, get userinfo, get logouturi etc.
Your API does not have such an endpoint, so that's what the error says.
If you want to implement the whole oidc-spec on your own, go for it, but I wouldn't recommend it, it's kind of complex. As I see it, .net core does only implement an openidconnect-client and you're free to choose the server implementation as long as it implements the spec.
Alternatively, have a look at JwtBearer, which is a more lightweight approach to authentication and more easy to implement yourself (and, best of: you could easily change to oidc later on). Good starting points might be these blogposts.
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
}).AddCookie(CookieAuthenticationDefaults.AuthenticationScheme,
options =>
{
options.LoginPath = new PathString("/");
options.AccessDeniedPath = "/Identity/Account/AccessDenied";
options.Cookie = new CookieBuilder()
{
SecurePolicy = CookieSecurePolicy.SameAsRequest,
Path = "/"
};
options.SlidingExpiration = true;
options.ExpireTimeSpan = TimeSpan.FromMinutes(sessionTimeout);
}).AddOpenIdConnect(options =>
{
options.ClientId = Configuration.GetValue<string>("oidc:ClientId");
options.ClientSecret = Configuration["oidc:ClientSecret"];
options.CallbackPath = new PathString("/auth/callback");
options.GetClaimsFromUserInfoEndpoint = true;
options.Authority = Configuration["oidc:Authority"];
options.SignedOutRedirectUri = "/";
options.RequireHttpsMetadata = false;
options.SaveTokens = true;
options.UseTokenLifetime = true;
var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(Configuration["oidc:ClientSecret"]));
options.TokenValidationParameters = new TokenValidationParameters
{
RequireSignedTokens = true,
IssuerSigningKey = signingKey,
ValidateAudience = true,
ValidateIssuer = true,
ValidateIssuerSigningKey = true,
};
options.ResponseType = OpenIdConnectResponseType.Code;
options.AuthenticationMethod = OpenIdConnectRedirectBehavior.RedirectGet;
options.Scope.Clear();
options.Scope.Add("openid");
options.Scope.Add("email");
options.Scope.Add("profile");
options.Events = new OpenIdConnectEvents()
{
OnTicketReceived = context =>
{
var identity = context.Principal.Identity as ClaimsIdentity;
if (identity != null)
{
if (!context.Principal.HasClaim(c => c.Type == ClaimTypes.Name) &&
identity.HasClaim(c => c.Type == "name"))
identity.AddClaim(new Claim(ClaimTypes.Name, identity.FindFirst("name").Value));
if (context.Properties.Items.ContainsKey(".TokenNames"))
{
string[] tokenNames = context.Properties.Items[".TokenNames"].Split(';');
foreach (var tokenName in tokenNames)
{
string tokenValue = context.Properties.Items[$".Token.{tokenName}"];
identity.AddClaim(new Claim(tokenName, tokenValue));
}
}
}
var cp = new ClaimsPrincipal(identity);
context.Principal = cp;
return Task.CompletedTask;
},
//OnTokenValidated = context =>
//{
// ClaimsIdentity identity = (ClaimsIdentity)context.Principal.Identity;
// Claim Name = identity.FindFirst("preferred_username");
// Claim gender = identity.FindFirst(ClaimTypes.Gender);
// Claim sub = identity.FindFirst(ClaimTypes.NameIdentifier);
// var claimsToKeep = new List<Claim> { Name, gender, sub };
// var newIdentity = new ClaimsIdentity(claimsToKeep, identity.AuthenticationType);
// context.Principal = new ClaimsPrincipal(newIdentity);
// return Task.FromResult(0);
//},
OnAuthenticationFailed = context =>
{
context.Response.Redirect("/");
context.HandleResponse();
return Task.CompletedTask;
},
OnRedirectToIdentityProvider = context =>
{
context.ProtocolMessage.SetParameter("pfidpadapterid", Configuration["oidc:pfidpadapterid"]);
return Task.FromResult(0);
}
};
});

Having trouble with JWT token in ASP.NET Core app with multiple authentication schemes

I have an ASP.NET Core 2.0 app with both web pages and API. I'm using cookie authentication for web pages and now I want to use JWT Tokens for API methods.
I followed this article to set this up which does a pretty good job in walking us through the process: https://wildermuth.com/2017/08/19/Two-AuthorizationSchemes-in-ASP-NET-Core-2
I am, however, getting a strange response in my API method that I set up to use JWT token. When I hit the API method, I do get a Status 200 response in Postman but I never hit the break point I set up in the API method. More interestingly, Visual Studio debugger is showing a successful request and when I click it, I see a response code 302 even though Postman shows me a 200 -- see below:
Stranger still, the response I see in Postman is the HTML code of my login page which I redirect users to if they're not authenticated.
Here's my configuration for authentication. As you'll see below, I use social logins as well so my configuration is a bit long so I put all the configuration in a separate file in order not to clutter my Startup.cs. I simply call this from the ConfigureServices() method.
public static void MyAppAuthenticationConfig(IServiceCollection services, IConfiguration configuration)
{
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = "my_cookie";
options.DefaultChallengeScheme = "my_cookie";
})
.AddCookie("my_cookie", options =>
{
options.AccessDeniedPath = "/Home/Denied";
options.LoginPath = "/Login";
})
.AddCookie("social_auth_cookie")
.AddOAuth("LinkedIn", options =>
{
options.SignInScheme = "social_auth_cookie";
options.ClientId = "my_client_id";
options.ClientSecret = "my_secret";
options.CallbackPath = "/linkedin-callback";
options.AuthorizationEndpoint = "https://www.linkedin.com/oauth/v2/authorization";
options.TokenEndpoint = "https://www.linkedin.com/oauth/v2/accessToken";
options.UserInformationEndpoint = "https://api.linkedin.com/v1/people/~:(id,first-name,last-name,email-address,picture-url,picture-urls::(original))";
options.Scope.Add("r_basicprofile");
options.Scope.Add("r_emailaddress");
options.Events = new OAuthEvents
{
OnCreatingTicket = OnCreatingTicketLinkedInCallBack,
OnTicketReceived = OnTicketReceivedCallback
};
})
.AddFacebook(options =>
{
options.SignInScheme = "social_auth_cookie";
options.AppId = "my_app_id";
options.AppSecret = "my_secret";
options.Events = new OAuthEvents
{
OnCreatingTicket = OnCreatingTicketFacebookCallback,
OnTicketReceived = OnTicketReceivedCallback
};
})
.AddGoogle(options =>
{
options.SignInScheme = "social_auth_cookie";
options.ClientId = "my_id.apps.googleusercontent.com";
options.ClientSecret = "my_secret";
options.CallbackPath = "/google-callback";
options.Events = new OAuthEvents
{
OnCreatingTicket = OnCreatingTicketGoogleCallback,
OnTicketReceived = OnTicketReceivedCallback
};
})
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, jwtBearerOptions =>
{
jwtBearerOptions.RequireHttpsMetadata = false;
jwtBearerOptions.SaveToken = true;
jwtBearerOptions.Challenge = JwtBearerDefaults.AuthenticationScheme;
jwtBearerOptions.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("my_secret_key")),
ValidateIssuer = true,
ValidIssuer = "myapp-api",
ValidateAudience = true,
ValidAudience = "myapp-client",
ValidateLifetime = true,
ClockSkew = TimeSpan.FromMinutes(5)
};
});
}
And this is the API method where I want to use JWT Token.
[HttpGet("testit")]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public IActionResult Test()
{
return Ok("Hello World!");
}
I have a break point where I return Ok("Hello World!"); but like I said I never hit it.
What am I doing wrong here?
UPDATE:
When I inspect the ChallangeResult("Bearer"), this is what I see:
And here's how I'm sending my request in Postman:

Get claims from JWT token in ASP.NET Core 2.0 API

I'm using mixed authentication in my ASP.NET Core 2.0 Web and API app. Meaning both cookies and now adding JWT token.
The web part of the app uses cookies and in the API part, I want to use JWT token.
My question is how do I get the claims from JWT token? In my web controllers, I can simply use HttpContext.User; to get the claims stored in a cookie. How do I handle it in my API methods where I want to use JWT token?
Here's my AuthenticationBuilder:
public static void MyAuthenticationConfig(IServiceCollection services, IConfiguration configuration)
{
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = "myApp_cookie";
options.DefaultChallengeScheme = "myApp_cookie";
})
.AddCookie("myApp_cookie", options =>
{
options.AccessDeniedPath = "/Unauthorized";
options.LoginPath = "/Login";
})
.AddCookie("social_auth_cookie")
.AddOAuth("LinkedIn", options =>
{
options.SignInScheme = "social_auth_cookie";
options.ClientId = "my_client_id";
options.ClientSecret = "my_secret";
options.CallbackPath = "/linkedin-callback";
options.AuthorizationEndpoint = "https://www.linkedin.com/oauth/v2/authorization";
options.TokenEndpoint = "https://www.linkedin.com/oauth/v2/accessToken";
options.UserInformationEndpoint = "https://api.linkedin.com/v1/people/~:(id,first-name,last-name,email-address,picture-url,picture-urls::(original))";
options.Scope.Add("r_basicprofile");
options.Scope.Add("r_emailaddress");
options.Events = new OAuthEvents
{
OnCreatingTicket = OnCreatingTicketLinkedInCallBack,
OnTicketReceived = OnTicketReceivedCallback
};
})
.AddFacebook(options =>
{
options.SignInScheme = "social_auth_cookie";
options.AppId = "my_app_is";
options.AppSecret = "my_secret";
options.Events = new OAuthEvents
{
OnCreatingTicket = OnCreatingTicketFacebookCallback,
OnTicketReceived = OnTicketReceivedCallback
};
})
.AddGoogle(options =>
{
options.SignInScheme = "social_auth_cookie";
options.ClientId = "my_id.apps.googleusercontent.com";
options.ClientSecret = "my_secret";
options.CallbackPath = "/google-callback";
options.Events = new OAuthEvents
{
OnCreatingTicket = OnCreatingTicketGoogleCallback,
OnTicketReceived = OnTicketReceivedCallback
};
})
.AddJwtBearer("JwtBearer", jwtBearerOptions =>
{
jwtBearerOptions.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("my_secret")),
ValidateIssuer = true,
ValidIssuer = "my-api",
ValidateAudience = true,
ValidAudience = "my-client",
ValidateLifetime = true,
ClockSkew = TimeSpan.FromMinutes(5)
};
});
}
Normally the claims of JWT are automatically added to the ClaimsIdentity.
Source:
https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/af5e5c2b0100e8348c63e2d2bb45612e2080841e/src/System.IdentityModel.Tokens.Jwt/JwtSecurityTokenHandler.cs#L1110).
So you should be able to just use 'User' property of the base 'Controller' class.
public async Task<IActionResult> Get()
{
// TODO Move 'Claims' extraction code to an extension method
var address = User.Claims.Where('GET THE NEEDED CLAIM');
...
}
I never had any problems getting the claims from a JWT token.
But I only used IdentityServer4.AccessTokenValidation so far. But internally it uses the Microsoft JWT Handler afaik.
Couldn't comment because my post would be to long, so making a seperate post instead.
If you follow the guide/link ErazerBrecht posted, the claims are indeed stored in the ClaimsPrincipal User. I created an extension method to retrieve the claim.
Note that I use an Enum for registering my claims. I use a dictionary to pass my claims to the method that generates my token, so my claim key should always be unique.
The extension method:
public static string GetClaim(this ClaimsPrincipal claimsPrincipal, JwtClaim jwtClaim)
{
var claim = claimsPrincipal.Claims.Where(c => c.Type == jwtClaim.ToString()).FirstOrDefault();
if (claim == null)
{
throw new JwtClaimNotFoundException(jwtClaim);
}
return claim.Value;
}
Call it like:
var userId = User.GetClaim(JwtClaim.UserId);

Resources