I am going to implement my app in .Net Core, using RabbitMQ and MassTransit in Request/Response pattern.
Here is the code for receiver (it receives the username and password and then sends the username and a provider key to the client):
//BusConfiguration.cs
public static IBusControl ConfigureBus(
Action<IRabbitMqBusFactoryConfigurator, IRabbitMqHost> registrationAction = null)
{
return Bus.Factory.CreateUsingRabbitMq(cfg =>
{
var host = cfg.Host(new Uri(RabbitMqConstants.RabbitMqUri), hst =>
{
hst.Username(RabbitMqConstants.UserName);
hst.Password(RabbitMqConstants.Password);
});
registrationAction?.Invoke(cfg, host);
});
}
public void ConfigureBus()
{
bus = BusConfigurator.ConfigureBus((cfg, host) =>
{
cfg.ReceiveEndpoint(host, RabbitMqConstants.OAuth2ServiceQueue, e =>
{
e.Consumer<CreateUserCommandConsumer>();
});
});
TaskUtil.Await(() => bus.StartAsync());
}
//CreateUserCommandConsumer.cs
public class CreateUserCommandConsumer : IConsumer<ICreateUserCommand>
{
public async Task Consume(ConsumeContext<ICreateUserCommand> context)
{
await context.RespondAsync<IUserCreatedEvent>(new
{
UserName = context.Message.UserName,
ProviderKey = "q1w2e3d3r"
});
}
}
The command and event classes are like below:
//ICreateUserCommand.cs
namespace WebHost.My.ServiceBus.Messages
{
public interface ICreateUserCommand
{
string UserName { get; set; }
string Password { get; set; }
}
}
//IUserCreatedEvent.cs
namespace WebHost.My.ServiceBus.Messages.CreateUser
{
public interface IUserCreatedEvent
{
string UserName { get; set; }
string ProviderKey { get; set; }
}
}
And here is the code for my client (sends request for user creation):
var bus = BusConfigurator.ConfigureBus((cfg, host) =>
{
cfg.ReceiveEndpoint(host, "profile.test.service", e =>
{
});
});
TaskUtil.Await(() => bus.StartAsync());
try
{
IRequestClient<ICreateUserCommand, IUserCreatedEvent> client = CreateRequestClient(bus);
var userName = "username";
var password = "password";
Task.Run(async () =>
{
var response = await client.Request(new CreateUserCommand()
{
UserName = userName,
Password = password
});
Console.WriteLine("User Provider key: {0}", response.ProviderKey);
Console.WriteLine("User Username: {0}", response.UserName);
}).Wait();
}
catch (Exception ex)
{
Console.WriteLine("Exception!!! OMG!!! {0}", ex);
}
finally
{
bus.Stop();
}
}
static IRequestClient<ICreateUserCommand, IUserCreatedEvent> CreateRequestClient(IBusControl busControl)
{
var serviceAddress = new Uri(RabbitMqConstants.RabbitMqUri + RabbitMqConstants.OAuth2ServiceQueue);
var client =
busControl.CreateRequestClient<ICreateUserCommand, IUserCreatedEvent>(serviceAddress, TimeSpan.FromSeconds(60), TimeSpan.FromSeconds(60));
return client;
}
The point is that the two sides (request side and response side) are different projects with no .ddl in common. In other words, they do not share the IUserCreatedEvent and ICreateUserCommand interfaces. When running the server code (respondent), it creates an exchange named like "WebHost.My.ServiceBus.Messages:ICreateUserCommand" which is a combination of the namespace and interface name. Since I do not have such a namespace in my client side code, when the respondent sends the provider key and username, the message goes to a _skipped exchange and I cannot get the response.
As far as I searched and understood, the Command and Event interfaces must be shared between the two projects (requester and respondent), but I since I am coding my project API-based, I do not want them to share the namespaces!
How is it possible to overcome such a limitation in MassTransit?
Thanks so much
Related
Does anyone have a full implementation demo of reCaptcha V3 in ASP.NET?
I found this article: Google Recaptcha v3 example demo
At the moment I am using reCaptcha V2 with the following code:
public bool RecaptchaValidate()
{
string Response = Request.Form["g-recaptcha-response"];//Getting Response String Append to Post Method
bool Valid = false;
//Request to Google Server
var CaptchaSiteKey = Settings["NewUserRegCaptchaSecretSiteKey"].ToString();
HttpWebRequest req = (HttpWebRequest)WebRequest.Create
(" https://www.google.com/recaptcha/api/siteverify?secret=" + CaptchaSiteKey + "&response=" + Response);
try
{
//Google recaptcha Response
using (WebResponse wResponse = req.GetResponse())
{
using (StreamReader readStream = new StreamReader(wResponse.GetResponseStream()))
{
string jsonResponse = readStream.ReadToEnd();
JavaScriptSerializer js = new JavaScriptSerializer();
ReCaptchaObject data = js.Deserialize<ReCaptchaObject>(jsonResponse);// Deserialize Json
Valid = Convert.ToBoolean(data.success);
}
}
return Valid;
}
catch (WebException ex)
{
throw ex;
}
}
On the view.ascx page I have:
<%# Register TagPrefix="recaptcha" Namespace="Recaptcha" Assembly="Recaptcha" %>
<script src='https://www.google.com/recaptcha/api.js'></script>
<scrip>
var recap = grecaptcha.getResponse();
if (recap.length == 0) {
$("#verifyhuman").css("display", "block");
}
</script>
<div class="g-recaptcha" data-sitekey="<%=ReCaptchaPublicKey%>" id="recaptcha" data-callback="recaptchaCallback"></div>
The simplest implementation:
In your cshtml file (at the top)
#section Scripts
{
<script src="https://www.google.com/recaptcha/api.js?render=your site key"></script>
<script>
grecaptcha.ready(function () {
grecaptcha.execute('your site key', { action: 'homepage' }).then(function (token) {
document.getElementById("foo").value = token;
});
});
</script>
}
In your cshtml, inside the form (just before </form>):
<input type="hidden" id="foo" name="foo" />
A function inside your Pagemodel class. See the documentation for the response object:
public static bool ReCaptchaPassed(string gRecaptchaResponse)
{
HttpClient httpClient = new HttpClient();
var res = httpClient.GetAsync($"https://www.google.com/recaptcha/api/siteverify?secret=your secret key no quotes&response={gRecaptchaResponse}").Result;
if (res.StatusCode != HttpStatusCode.OK)
{
return false;
}
string JSONres = res.Content.ReadAsStringAsync().Result;
dynamic JSONdata = JObject.Parse(JSONres);
if (JSONdata.success != "true" || JSONdata.score <= 0.5m)
{
return false;
}
return true;
}
Finally, inside your OnPostAsync() handler, at the top:
if (!ModelState.IsValid)
{
return Page();
}
else
{
if (!ReCaptchaPassed(Request.Form["foo"]))
{
ModelState.AddModelError(string.Empty, "You failed the CAPTCHA.");
return Page();
}
}
Edit : I have added a demo project . Check this github repository .
https://github.com/NIHAR-SARKAR/GoogleRecaptchav3-example-In-asp.net
From frontend (.aspx page) you need to send ajax request to pass the token to backend server . Using "recaptcha.execute" U can get the response , and pass the token using ajax request .Please check the code block .
<script src="http://www.google.com/recaptcha/api.js?render=recaptchaSiteKey"></script>
<script>
grecaptcha.ready(function() {
grecaptcha.execute('recaptchaSiteKey', {action: 'homepage'}).then(function(token) {
$.ajax({
//pass the toket to Webmethod using Ajax
});
});
});
</script>
Reference link:
https://developers.google.com/recaptcha/docs/verify
https://developers.google.com/recaptcha/docs/display#js_api
Now in the aspx.cs you need to write a "[WebMethod]" to receive the token from Ajax request .
[WebMethod]
public static void CaptchaVerify(string token)
{
var responseString = RecaptchaVerify(token);
ResponseToken response = new ResponseToken();
response = Newtonsoft.Json.JsonConvert.DeserializeObject<ResponseToken>(responseString.Result);
}
To get the response from google recapcha api u need to use async call using httpClient . you also need to create a class which will contain same properties like the response string . After getting the "responseString" u need to convert the response to ResponseToken object by using Newtonsoft.Json.
response = Newtonsoft.Json.JsonConvert.DeserializeObject<ResponseToken>(responseString.Result);
private string apiAddress = "https://www.google.com/recaptcha/api/siteverify";
private string recaptchaSecret = googleRecaptchaSecret;
public async Task<string> RecaptchaVerify(string recaptchaToken)
{
string url = $"{apiAddress}?secret={recaptchaSecret}&response={recaptchaToken}";
using (var httpClient = new HttpClient())
{
try
{
string responseString= httpClient.GetStringAsync(url).Result;
return responseString;
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
}
}
public class ResponseToken
{
public DateTime challenge_ts { get; set; }
public float score { get; set; }
public List<string> ErrorCodes { get; set; }
public bool Success { get; set; }
public string hostname { get; set; }
}
The accepted answer on this page is totally wrong!!! Google returns a score between 0 and 1 to indicate whether the submission is likely to be a bot or likely to be a human.
The success property returned only means that the recaptcha token was processed correctly.
It is the score property that should be checked, not the success property
These lines are the probelem
if (JSONdata.success != "true")
return false;
return true;
The actual score to compare will probably be in a variable that can be adjusted if need be. Google recommends starting with 0.5.
So the code should change to something like:
var recaptchaScore = 0.5m; // this could be in appSettings or whereever/however you are storing your constants
if (JSONdata.success != "true" || JSONdata.score <= recaptchaScore)
return false;
return true;
Of course you will likely want to add logging etc to this answer but this is the bare logic that is required.
The accepted answer isn't following the Google's spec for sending the response and checking the action. Its Http requests will exhaust the number of sockets also. This is my implementation.
Browser
// Could be called from an event or another piece of code.
function FunctionToCall(term) {
// Google reCaptcha check
grecaptcha.ready(function() {
grecaptcha.execute(reCaptchaSiteKey, {action: "search"}).then(function(token) {
// You can take the response token Google returns, check it server side using
// the GoogleReCaptcha class and respond with a pass or fail. If a pass, run a block of code client side.
// { ... block of code ... }
// Or if you want to secure an endpoint that your sending request too.
// Send the response token with the request to your endpoint and check the response token server side and respond with a pass or fail.
// Use the repsonse to show a message or redirect site, etc
});
});
}
Server
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
public class GoogleReCaptcha
{
public class ReCaptchaResponse
{
public bool success { get; set; }
public double score { get; set; }
public string action { get; set; }
public DateTime challenge_ts { get; set; }
public string hostname { get; set; }
[JsonProperty("error-codes")]
public List<string> error_codes { get; set; }
}
public static async Task<(ReCaptchaResponse Response, bool HasPassed)> ReCaptchaPassed(string secretKey, string gRecaptchaToken, string expected_action)
{
try
{
// validate
if (string.IsNullOrWhiteSpace(secretKey) || string.IsNullOrWhiteSpace(gRecaptchaToken) || string.IsNullOrWhiteSpace(expected_action))
return (null, false);
// we use HttpClientFactory to avoid exhausting number of sockets available
var httpClient = HttpClientFactory.Create();
var verifyUrl = "https://www.google.com/recaptcha/api/siteverify";
var parameters = new Dictionary<string, string>
{
{"secret", secretKey},
{"response", gRecaptchaToken}
//{"remoteip", "ip" } <= this is optional
};
using (HttpContent formContent = new FormUrlEncodedContent(parameters))
{
using (var response = await httpClient.PostAsync(verifyUrl, formContent).ConfigureAwait(false))
{
// check HTTP response code
if (response.StatusCode != HttpStatusCode.OK)
return (null, false);
// get reCaptcha response
string gRecaptchaJsonresult = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
if (string.IsNullOrWhiteSpace(gRecaptchaJsonresult))
return (null, false);
// check reCaptcha response is successful
var recaptcha_response = JsonConvert.DeserializeObject<ReCaptchaResponse>(gRecaptchaJsonresult);
if (recaptcha_response == null)
{
//Logging.Log(new Logging.LogItem { Msg = $"Google RecCaptcha response is null" }, DefaultLogValues);
return (recaptcha_response, false);
}
if (!recaptcha_response.success)
{
var errors = string.Join(",", recaptcha_response.error_codes);
//Logging.Log(new Logging.LogItem { Msg = $"Google RecCaptcha error codes:\n{errors}" }, DefaultLogValues);
return (recaptcha_response, false);
}
// check reCaptcha response action
if (recaptcha_response.action.ToUpper() != expected_action.ToUpper())
{
//Logging.Log(new Logging.LogItem { Msg = $"Google RecCaptcha action doesn't match:\nExpected action: {expected_action} Given action: {recaptcha_response.action}" }, DefaultLogValues);
return (recaptcha_response, false);
}
// response score
// anything less than 0.5 is a bot
if (recaptcha_response.score < 0.5)
return (recaptcha_response, false);
else
return (recaptcha_response, true);
}
}
}
catch (Exception ex)
{
//Logging.Log(ex, DefaultLogValues);
// default to false
return (null, false);
}
}
}
You would call it like so..
var reCaptchaTask = GoogleReCaptcha.ReCaptchaPassed(Settings.GoogleReCaptcha.secret_key, SearchReq.gRecaptchaToken, "search");
Make sure to put your keys in a settings file and not in the code.
There are several Recaptcha libraries available for ASP.Net. I chose to use reCAPTCHA.AspNetCore because it provides an HtmlHelper.
Please note that this library only supports one ReCatpcha per page, and it doesn't support Recaptcha v3 passive monitoring on non-form pages.
I'm working in ASP.NET Core and using MailKit for email functionality. The method I've previously used is no longer working.
I have an SmtpOptions.cs class:
public class SmtpOptions
{
public string Server { get; set; } = "smtp.gmail.com"; //Gmail limited to 2000 emails per day
public int Port { get; set; } = 465; //default for SSL using GMail
public string User { get; set; } = "myEmail#gmail.com"; //must match server domain
public string Password { get; set; } = "myPwd";
public bool UseSsl { get; set; } = true; //gmail requires SSL
public bool RequiresAuthentication { get; set; } = true; //gmail requires authentication
public string PreferredEncoding { get; set; } = string.Empty;
}
and EmailSender.cs class:
public class EmailSender
{
public EmailSender()
{
}
public async Task SendEmailAsync(
SmtpOptions smtpOptions,
string to,
string from,
string subject,
string plainTextMessage,
string htmlMessage,
string replyTo = null)
{
if (string.IsNullOrWhiteSpace(to))
{
throw new ArgumentException("no to address provided");
}
if (string.IsNullOrWhiteSpace(from))
{
throw new ArgumentException("no from address provided");
}
if (string.IsNullOrWhiteSpace(subject))
{
throw new ArgumentException("no subject provided");
}
var hasPlainText = !string.IsNullOrWhiteSpace(plainTextMessage);
var hasHtml = !string.IsNullOrWhiteSpace(htmlMessage);
if (!hasPlainText && !hasHtml)
{
throw new ArgumentException("no message provided");
}
var m = new MimeMessage();
m.From.Add(new MailboxAddress("", from));
if (!string.IsNullOrWhiteSpace(replyTo))
{
m.ReplyTo.Add(new MailboxAddress("", replyTo));
}
m.To.Add(new MailboxAddress("", to));
m.Subject = subject;
//m.Importance = MessageImportance.Normal;
//Header h = new Header(HeaderId.Precedence, "Bulk");
//m.Headers.Add()
BodyBuilder bodyBuilder = new BodyBuilder();
if (hasPlainText)
{
bodyBuilder.TextBody = plainTextMessage;
}
if (hasHtml)
{
bodyBuilder.HtmlBody = htmlMessage;
}
m.Body = bodyBuilder.ToMessageBody();
using (var client = new SmtpClient())
{
await client.ConnectAsync(
smtpOptions.Server,
smtpOptions.Port,
smtpOptions.UseSsl)
.ConfigureAwait(false);
// Note: since we don't have an OAuth2 token, disable
// the XOAUTH2 authentication mechanism.
client.AuthenticationMechanisms.Remove("XOAUTH2");
// Note: only needed if the SMTP server requires authentication
if (smtpOptions.RequiresAuthentication)
{
await client.AuthenticateAsync(smtpOptions.User, smtpOptions.Password)
.ConfigureAwait(false);
}
await client.SendAsync(m).ConfigureAwait(false);
await client.DisconnectAsync(true).ConfigureAwait(false);
}
}
public async Task SendMultipleEmailAsync(
SmtpOptions smtpOptions,
string toCsv,
string from,
string subject,
string plainTextMessage,
string htmlMessage)
{
if (string.IsNullOrWhiteSpace(toCsv))
{
throw new ArgumentException("no to addresses provided");
}
if (string.IsNullOrWhiteSpace(from))
{
throw new ArgumentException("no from address provided");
}
if (string.IsNullOrWhiteSpace(subject))
{
throw new ArgumentException("no subject provided");
}
var hasPlainText = !string.IsNullOrWhiteSpace(plainTextMessage);
var hasHtml = !string.IsNullOrWhiteSpace(htmlMessage);
if (!hasPlainText && !hasHtml)
{
throw new ArgumentException("no message provided");
}
var m = new MimeMessage();
m.From.Add(new MailboxAddress("", from));
string[] adrs = toCsv.Split(',');
foreach (string item in adrs)
{
if (!string.IsNullOrEmpty(item)) { m.To.Add(new MailboxAddress("", item)); ; }
}
m.Subject = subject;
m.Importance = MessageImportance.High;
BodyBuilder bodyBuilder = new BodyBuilder();
if (hasPlainText)
{
bodyBuilder.TextBody = plainTextMessage;
}
if (hasHtml)
{
bodyBuilder.HtmlBody = htmlMessage;
}
m.Body = bodyBuilder.ToMessageBody();
using (var client = new SmtpClient())
{
await client.ConnectAsync(
smtpOptions.Server,
smtpOptions.Port,
smtpOptions.UseSsl).ConfigureAwait(false);
// Note: since we don't have an OAuth2 token, disable
// the XOAUTH2 authentication mechanism.
client.AuthenticationMechanisms.Remove("XOAUTH2");
// Note: only needed if the SMTP server requires authentication
if (smtpOptions.RequiresAuthentication)
{
await client.AuthenticateAsync(
smtpOptions.User,
smtpOptions.Password).ConfigureAwait(false);
}
await client.SendAsync(m).ConfigureAwait(false);
await client.DisconnectAsync(true).ConfigureAwait(false);
}
}
}
That are used in a function call:
public async void ContactMessage(string title, string message, string toEmail)
{
string thisMessage = "No Message Provided";
if (!String.IsNullOrEmpty(message)) { thisMessage = message; } //in case empty form
string thisTitle = title;
//create email objects
EmailSender emailSender = new EmailSender();
SmtpOptions smtpOptions = new SmtpOptions(); //default settings ok
string fromEmail = smtpOptions.User;
string subjectLine = "Message Title";
await emailSender.SendEmailAsync(smtpOptions, toEmail, fromEmail, subjectLine, thisMessage, "");
}
As stated, this method has worked before but now I'm getting a failure of Gmail to respond. I've checked and have IMAP and POP enabled.
The specific exception reads:
"System.Net.Internals.SocketExceptionFactory.ExtendedSocketE‌​xception: 'A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond'"
This code has not been deployed but causes this error in the test environment. Moving to a differently firewalled system causes the error "Mailkit.Security.Authentication; MailKit authentication is too weak"
Testing with Yahoo! smtp allows the above code to work so it seems like the compatibility between Mailkit and Gmail has changed. What is the appropriate Mailkit configuration for using Gmail?
The email account settings were not configured to allow use of less-secure apps. The two solutions are:
1) Obtain OAuth 2.0 credentials within the EmailSender.cs class, or
2) Change email account settings to allow less secure apps
There are more details at:
How to send email by using MailKit?
Generally, when you get an AuthenticationException saying that the AUTH mechanism was too weak, it means that the client tried to use the PLAIN or LOGIN mechanisms and the server only allows those mechanisms to be used over SSL.
Make sure you are connecting on port 465 with SSL enabled or on port 587 with SecureSocketOptions.StartTls as the third argument.
I want to register a user, through an external provider (like facebook), in order to get the information I need, I configure FacebookProvider as follows
var options = new FacebookAuthenticationOptions {
AppId = "***",
AppSecret = "***",
Scope = { "email" },
Provider = new FacebookAuthenticationProvider {
OnAuthenticated = (context) => {
foreach (var x in context.User)
{
var claimType = string.Format("urn:facebook:{0}", x.Key);
string claimValue = x.Value.ToString();
if (!context.Identity.HasClaim(claimType, claimValue))
context.Identity.AddClaim(new System.Security.Claims.Claim(claimType, claimValue, XmlSchemaString, "Facebook"));
}
return Task.FromResult(0);
}
}
};
options.Fields.Add("id");
options.Fields.Add("name");
options.Fields.Add("email");
options.SignInAsAuthenticationType = DefaultAuthenticationTypes.ExternalBearer;
app.UseFacebookAuthentication(options);
in the OnAuthenticated while debugging I see all the requested fields but when I call RegisterExternal from postman as follow pic
RegisterExternal call postman
GetExternalLoginInfoAsync returns null
var info = await Authentication.GetExternalLoginInfoAsync();
if (info == null)
{
return InternalServerError();
}
so how to retrieve query fields such as email? I think all the necessary information is stored in the cookies, but how do I transfer them to the server and extract Identity instance?
all nuget packages have been updated to the latests versions
p.s. I plan to work with the API from the iOS app
I found solution.
Changed ExternalLoginData class as follows
private class ExternalLoginData
{
...
// here added new field
public IList<Claim> Claims { get; private set; }
public static ExternalLoginData FromIdentity(ClaimsIdentity identity)
{
...
return new ExternalLoginData
{
...
// here added claims setting
Claims = identity.Claims.ToList()
};
}
}
Changed ExternalLogin callback as follows
public async Task<IHttpActionResult> GetExternalLogin(string provider, string error = null)
{
...
if (hasRegistered)
{
...
}
else
{
// here replaced getting claims by Claims field
IEnumerable<Claim> claims = externalLogin.Claims;
//IEnumerable<Claim> claims = externalLogin.GetClaims();
ClaimsIdentity identity = new ClaimsIdentity(claims, OAuthDefaults.AuthenticationType);
Authentication.SignIn(identity);
}
return Ok();
}
As a result we receive a bearer token. Extracting Identity from it we receive earlier saved claims.
In my Startup.Auth.cs:
private static void ConfigSignalR(IAppBuilder appBuilder)
{
appBuilder.MapSignalR();
var idProvider = new PrincipalUserIdProvider();
GlobalHost.DependencyResolver.Register(typeof(IUserIdProvider), () => idProvider);
}
My UserHub.cs:
public class UserHub : Hub
{
}
On the server-side, in one of my API Controller action (a Put related to a Grid Update):
[...]
var userHub = GlobalHost.ConnectionManager.GetHubContext<UserHub>();
// Line below does not work
// userHub.Clients.User(userId).send("Hi");
// But this line below works when sending the message to everybody
userHub.Clients.All.send("Hi");
return Request.CreateResponse(HttpStatusCode.OK);
On the JS View client-side:
#Request.IsAuthenticated
{
<script>
$(function() {
var userHub = $.connection.userHub;
console.log(userHub.client);
userHub.client.send = function(message) {
alert('received: ' + message);
};
$.connection.hub.start().done(function() {
});
});
</script>
}
Why when passing the userId my client receives nothing?
(also tried passing the userName, with the same outcome).
[EDIT]
Technically the right way to achieve that is to leverage the implementation of the IUserIdProvider:
https://learn.microsoft.com/en-us/aspnet/signalr/overview/guide-to-the-api/mapping-users-to-connections#IUserIdProvider
SignalR - Sending a message to a specific user using (IUserIdProvider) *NEW 2.0.0*
However, I've noticed that in my case the User property of the IRequest object passed to the GetUserId method is always set to null...
The solution was actually already given for another issue, right here: https://stackoverflow.com/a/22028296/4636721
The problem was all about the initialization order in the Startup.Auth.cs:
SignalR must be initialized after the cookies and the OwinContext initialization, such as that IUserIdProvider passed to GlobalHost.DependencyResolver.Register receives a IRequest containing a non-null User for its GetUserId method:
public partial class Startup
{
public void ConfigureAuth(IAppBuilder appBuilder)
{
// Order matters here...
// Otherwise SignalR won't get Identity User information passed to Id Provider...
ConfigOwinContext(appBuilder);
ConfigCookies(appBuilder);
ConfigSignalR(appBuilder);
}
private static void ConfigOwinContext(IAppBuilder appBuilder)
{
appBuilder.CreatePerOwinContext(ApplicationDbContext.Create);
appBuilder.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
appBuilder.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
appBuilder.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
appBuilder.CreatePerOwinContext(LdapAdEmailAuthenticator.Create);
}
private static void ConfigCookies(IAppBuilder appBuilder)
{
appBuilder.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>
(
TimeSpan.FromHours(4),
(manager, user) => user.GenerateUserIdentityAsync(manager)
)
}
});
appBuilder.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
appBuilder.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));
appBuilder.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
}
private static void ConfigSignalR(IAppBuilder appBuilder)
{
appBuilder.MapSignalR();
var idProvider = new HubIdentityUserIdProvider();
GlobalHost.DependencyResolver.Register(typeof(IUserIdProvider), () => idProvider);
}
}
Using the IUserIdProvider below, I explicit declared that I want to use the UserId and not the UserName as given by the default implementation of the IUserIdProvider, aka PrincipalUserIdProvider:
public class HubIdentityUserIdProvider : IUserIdProvider
{
public string GetUserId(IRequest request)
{
return request == null
? throw new ArgumentNullException(nameof(request))
: request.User?.Identity?.GetUserId();
}
}
I am trying to use the new User Id provider specified in signalr 2 to send messages to a specific user. When I call the Clients.All method, I see this working as my javascript code gets called from the server and the ui produces some expected text for my test case. However, when I switch to Clients.User the client side code is never called from the server. I followed the code outlined in this example: SignalR - Sending a message to a specific user using (IUserIdProvider) *NEW 2.0.0*.
NotificationHub.cs:
public class NotificationHub : Hub
{
[Authorize]
public void NotifyUser(string userId, int message)
{
Clients.User(userId).DispatchMessage(message);
}
public override Task OnConnected()
{
return base.OnConnected();
}
public override Task OnDisconnected(bool stopCalled)
{
return base.OnDisconnected(stopCalled);
}
public override Task OnReconnected()
{
return base.OnReconnected();
}
}
IUserIdProvider.cs:
public class UserIdProvider : IUserIdProvider
{
MemberService _memberService;
public UserIdProvider()
{
}
public string GetUserId(IRequest request)
{
long UserId = 0;
if (request.User != null && request.User.Identity != null &&
request.User.Identity.Name != null)
{
var currenUser = Task.Run(() => _memberService.FindByUserName(request.User.Identity.Name)).Result;
UserId = currenUser.UserId;
}
return UserId.ToString();
}
}
Startup.cs
HttpConfiguration config = GlobalConfiguration.Configuration;
config.Routes.MapHttpRoute(
"Default2",
"api/{controller}/{action}/{id}",
new { id = RouteParameter.Optional });
config.Routes.MapHttpRoute(
"DefaultApi2",
"api/{controller}/{id}",
new { id = RouteParameter.Optional });
app.Map("/signalr", map =>
{
map.UseCors(CorsOptions.AllowAll);
var idProvider = new UserIdProvider();
GlobalHost.DependencyResolver.Register(typeof(IUserIdProvider), () => idProvider);
map.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions()
{
Provider = new QueryStringOAuthBearerAuthenticationProvider()
});
var hubConfiguration = new HubConfiguration
{
};
map.RunSignalR(hubConfiguration);
});
app.MapSignalR();
QuerstringOAuthBearerAuthenticationProvider:
public class QueryStringOAuthBearerAuthenticationProvider
: OAuthBearerAuthenticationProvider
{
public override Task RequestToken(OAuthRequestTokenContext context)
{
if (context == null) throw new ArgumentNullException("context");
// try to find bearer token in a cookie
// (by default OAuthBearerAuthenticationHandler
// only checks Authorization header)
var tokenCookie = context.OwinContext.Request.Cookies["BearerToken"];
if (!string.IsNullOrEmpty(tokenCookie))
context.Token = tokenCookie;
return Task.FromResult<object>(null);
}
}
Do I need to map the user to the connections myself using the IUserIdProvider through the OnConnected, OnDisconnected, etc. or does this happen automatically behind the scenes? Is there someone wrong in my posted code that could be a problem as well? I am running signalr from the same environment as my web api rest services, don't know if this makes a difference and using the default bearer token setup web api is using.
It would be far easier for you to create a group based on the connectionid of the connecting client, in the onConnected event and broadcast to the group that matches the connected id, that way if the client disconnects, when they reconnect they would simply belong to a new group the themselves. Unless of course you are required to have an authenticated user.