The authentication in ChatRoom sample is actually very simple, you claim who you are and authentication API will give you a token with that name. This is not really useful in real-life applications, so in this tutorial you'll learn how to implement your own authentication and integrate with SignalR service.
GitHub provides OAuth APIs for third-party applications to authenticate with GitHub accounts. Let's use these APIs to allow users to login to our chat room with GitHub ID.
First step is to create a OAuth App in GitHub:
- Go to GitHub -> Settings -> Developer Settings, and click "New OAuth App".
- Fill in an application name, a description and a homepage URL (for this sample you can just enter any random URL)
- Authorization callback URL is the url GitHub will redirect you to after authentication. For now make it
https://localhost:5001/signin-github
. - Click "Register application" and you'll get an application with client ID and secret, you'll need them later when you implement the OAuth flow.
The first step of OAuth flow is to ask user to login with GitHub account. This can be done by redirect user to the GitHub login page.
Add a link in the chat room for user to login, when Http status code is 401
(unauthorized):
if (error.statusCode && error.statusCode === 401) {
appendMessage(
"_BROADCAST_",
"You\"re not logged in. Click <a href="/login">here</a> to login with GitHub."
);
}
The link points to /login
which redirects to the GitHub OAuth page if you are not authenticated:
[HttpGet("login")]
public IActionResult Login()
{
if (User.Identity == null || !User.Identity.IsAuthenticated)
{
return Challenge(GitHubAuthenticationDefaults.AuthenticationScheme);
}
HttpContext.Response.Cookies.Append("githubchat_username", User.Identity.Name ?? "");
HttpContext.SignInAsync(User);
return Redirect("/");
}
GitHub will check whether you have already logged in and authorized the application, if not, it will ask you to login and show a dialog to let you authorize the application:
After you authorized the application, GitHub will return a code to the application by redirecting to the callback url of the application. AspNet.Security.OAuth.GitHub
package will handle the rest of the OAuth flow for us and redirect back to /login
page with the authenticated user identity.
For more details about GitHub OAuth flow, please refer to this article.
For more details about using Cookie Authentication in ASP.NET Core, please refer to this article.
Then let's update the hub to enforce authentication.
- Add
[Authorize]
attribute on theChatSampleHub
class. Then only authenticated user can access the/chat
endpoint. AnUnauthorized
error will be returned if user is not authenticated.
[Authorize]
public class ChatSampleHub : Hub
{
...
}
- In previous tutorial
BroadcastMessage()
method takes aname
parameter to let caller claim who he is, which is apparently not secure. Let's remove thename
parameter and read user identifier fromHub
class'sContext
member.:
public Task BroadcastMessage(string message)
{
return Clients.All.SendAsync("broadcastMessage", Context.User?.Identity?.Name, message);
}
Finally let's update the client code to handle Unauthorized
error and instruct the user to log in.
connection.start()
.then(function () {
onConnected(connection);
})
.catch(function (error) {
console.error(error.message);
if (error.statusCode && error.statusCode === 401) {
appendMessage(
"_BROADCAST_",
"You\"re not logged in. Click <a href="/login">here</a> to login with GitHub."
);
}
});
Now you can run the project and chat using your GitHub ID:
dotnet restore
dotnet user-secrets set Azure:SignalR:ConnectionString "<your connection string>"
dotnet user-secrets set GitHubClientId "<client_id>"
dotnet user-secrets set GitHubClientSecret "<client_secret>"
dotnet run
Deployment to Azure is the same as before, just you need to set two new settings we just added:
az webapp config appsettings set --resource-group <resource_group_name> --name <app_name> \
--setting GitHubClientId=<client_id>
az webapp config appsettings set --resource-group <resource_group_name> --name <app_name> \
--setting GitHubClientSecret=<client_secret>
And change the callback url of your GitHub app from localhost to the actual Azure website.
You can also deploy this sample via existing docker image
docker run -e Azure__SignalR__ConnectionString="<signalr-connection-string>" \
-e GITHUB_CLIENT_ID=<github-client-id> \
-e GITHUB_CLIENT_SECRET=<github-client-secret> \
-p 5000:80 mcr.microsoft.com/signalrsamples/githubchat:latest
It is possible to define different permission levels on hub methods. For example, we don't want everyone to be able to send message in a chat room. To achieve this, we can define a custom authorization policy:
services.AddAuthorization(options =>
{
options.AddPolicy("Microsoft_Only", policy => policy.RequireClaim("Company", "Microsoft"));
});
This policy requires the user to have a "Microsoft" company claim.
Then we can apply the policy to BroadcastMessage()
method:
[Authorize(Policy = "Microsoft_Only")]
public void BroadcastMessage(string message)
{
...
}
Now, if your GitHub account's company is not Microsoft, you cannot send messages in the chat room, but you can still see other user's messages.
If you use
send()
to call hub, SignalR won't send back a completion message so you won't know whether the call succeeded or not. So, if you want to get a confirmation of the hub invocation (for example in this case you want to know whether your call has enough permission) you need to useinvoke()
:connection.invoke("broadcastMessage", messageInput.value) .catch(e => appendMessage("_BROADCAST_", e.message));