So what I am trying to accomplish is that when a user sends a message, on the server-side I would like to know which "other" connections are available. I am using SignalR.
From those connections, I would like to know the roles of the users associated with each connection.
I am using ASP.NET Identity, SignalR, and C#.
Then I would like to filter out the connections based on user roles and broadcast a message to only those users.
Any ideas how this can be accomplished.
I think you can use this article to do that
Basically you can send the token from client to server (JWT in this case). JWT can contain all information you need.
The access token function you provide is called before every HTTP
request made by SignalR. If you need to renew the token in order to
keep the connection active (because it may expire during the
connection), do so from within this function and return the updated
token.
So that mean when ever user call signalr they will be authenticate again in the hub.
Or you can add claim to the user so you can detect who request and what role do they have
Sample from the document:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddAuthentication(options =>
{
// Identity made Cookie authentication the default.
// However, we want JWT Bearer Auth to be the default.
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
// Configure JWT Bearer Auth to expect our security key
options.TokenValidationParameters =
new TokenValidationParameters
{
LifetimeValidator = (before, expires, token, param) =>
{
return expires > DateTime.UtcNow;
},
ValidateAudience = false,
ValidateIssuer = false,
ValidateActor = false,
ValidateLifetime = true,
IssuerSigningKey = SecurityKey
};
// We have to hook the OnMessageReceived event in order to
// allow the JWT authentication handler to read the access
// token from the query string when a WebSocket or
// Server-Sent Events request comes in.
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/chat")))
{
// Read the token out of the query string
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddSignalR();
// Change to use Name as the user identifier for SignalR
// WARNING: This requires that the source of your JWT token
// ensures that the Name claim is unique!
// If the Name claim isn't unique, users could receive messages
// intended for a different user!
services.AddSingleton<IUserIdProvider, NameUserIdProvider>();
// Change to use email as the user identifier for SignalR
// services.AddSingleton<IUserIdProvider, EmailBasedUserIdProvider>();
// WARNING: use *either* the NameUserIdProvider *or* the
// EmailBasedUserIdProvider, but do not use both.
}
Related
In order to understand how to use Firebase Cloud Messaging, I am following this document:
https://firebase.google.com/docs/cloud-messaging/admin/send-messages?hl=en-us
Precisely I am looking at this section: Send to individual devices
I can see in the code that I need a registrationToken. My question is how do I concretely get one?
I first want to send a message to my own iPhone, laying on my desk and later to all iPhones of registered users.
When I work in IOS-Swift, you have to add this method in your AppDelegate.swift file:
func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String) {
print("Firebase registration token: \(fcmToken)")
let dataDict:[String: String] = ["token": fcmToken]
NotificationCenter.default.post(name: Notification.Name("FCMToken"), object: nil, userInfo: dataDict)
// TODO: If necessary send token to application server.
// Note: This callback is fired at each app startup and whenever a new token is generated.
}
If you need to access the token directly, use this:
InstanceID.instanceID().instanceID { (result, error) in
if let error = error {
print("Error fetching remote instange ID: \(error)")
} else if let result = result {
print("Remote instance ID token: \(result.token)")
self.instanceIDTokenMessage.text = "Remote InstanceID token: \(result.token)"
}
}
For more information visit:
https://firebase.google.com/docs/cloud-messaging/ios/client
When working with FCM notifications, the devices generate tokens, which are renewed every so often, so if you need to send a push to a device you must know your token, you must implement a class that inherits FirebaseMessagingService, and overwrite an onNewToken method, this method is called in the background every time the device token is updated.
/**
* Called if InstanceID token is updated. This may occur if the security of
* the previous token had been compromised. Note that this is called when the InstanceID token
* is initially generated so this is where you would retrieve the token.
*/
#Override
public void onNewToken(String token) {
Log.d(TAG, "Refreshed token: " + token);
// If you want to send messages to this application instance or
// manage this apps subscriptions on the server side, send the
// Instance ID token to your app server.
sendRegistrationToServer(token);
}
It is recommended that this token be sent to your server so that from there you can send the push to the devices with registered tokens. If you want to force a first token, you can use:
FirebaseInstanceId.getInstance().getInstanceId();
I have been working with AADB2C Authentication and struggling over redirecting the user to home page with id_token as fragment in redirect url. below is how i initiate authentication challenge in Account controller
public class AccountController : Controller
{
public AzureAdB2COptions AzureAdB2COptions { get; set; }
public AccountController(IOptions<AzureAdB2COptions> b2cOptions)
{
AzureAdB2COptions = b2cOptions.Value;
}
// GET: /<controller>/
[HttpGet]
public IActionResult SignUpSignIn()
{
var redirectUrl = "http://localhost:7878/dasdfsd/home";
return Challenge(
new AuthenticationProperties { RedirectUri = redirectUrl },
OpenIdConnectDefaults.AuthenticationScheme);
}
}
and below is where i redirect user to redirect url(http://localhost:7878/dgddfg/home) with id token fragmented along with url
public async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedContext context)
{
// Use MSAL to swap the code for an access token
// Extract the code from the response notification
var code = context.ProtocolMessage.Code;
string signedInUserID = context.Principal.FindFirst(ClaimTypes.NameIdentifier).Value;
TokenCache userTokenCache = new MSALSessionCache(signedInUserID, context.HttpContext).GetMsalCacheInstance();
ConfidentialClientApplication cca = new ConfidentialClientApplication(AzureAdB2COptions.ClientId, AzureAdB2COptions.Authority, AzureAdB2COptions.RedirectUri, new ClientCredential(AzureAdB2COptions.ClientSecret), userTokenCache, null);
try
{
AuthenticationResult result = await cca.AcquireTokenByAuthorizationCodeAsync(code, AzureAdB2COptions.ApiScopes.Split(' '));
context.HandleCodeRedemption(result.AccessToken, result.IdToken);
context.HandleResponse();
context.Response.Redirect("dfgfd/home#grant_type=" + context.ProtocolMessage.GrantType + "&id_token=" + result.IdToken);
}
catch (Exception)
{
//TODO: Handle
throw;
}
}
when user redirects to home page using javascript i get the id_token from url and calling Account controller action("GetAccessTokenAsync") to get access token using identity user
[HttpGet]
public async Task<string> GetAccessTokenAsync()
{
string signedInUserID = HttpContext.User.FindFirst(ClaimTypes.NameIdentifier).Value;
TokenCache userTokenCache = new MSALSessionCache(signedInUserID , HttpContext).GetMsalCacheInstance();
ConfidentialClientApplication cca = new ConfidentialClientApplication(AzureAdB2COptions.ClientId, AzureAdB2COptions.Authority, AzureAdB2COptions.RedirectUri, new ClientCredential(AzureAdB2COptions.ClientSecret), userTokenCache, null);
try
{
var userAccounts = await cca.GetAccountsAsync();
if (userAccounts == null && userAccounts.Count()==0)
{
throw new Exception("The User is NULL. Please clear your cookies and try again. Specifically delete cookies for 'login.microsoftonline.com'. See this GitHub issue for more details: https://github.com/Azure-Samples/active-directory-b2c-dotnet-webapp-and-webapi/issues/9");
}
AuthenticationResult result = await cca.AcquireTokenSilentAsync(AzureAdB2COptions.ApiScopes.Split(' '),userAccounts.FirstOrDefault());
return result.AccessToken;
}
catch (Exception ex)
{
//TODO: Handle
return "";
}
}
the problem here is the HttpContext.User in action method is null. but if i remove the part where i have written the redirect url with id_token appended and context.HandleResponse();in OnAuthorizationCodeReceived ,
user will be redirected without the id token yet when getAccessToken action executed HttpContext.User will not be null, user will have every detail including claims.
How can i redirect user with id token in url and also to have user without being null to get access token in controller method? Is it something to do with cookie?
I don't know why you need ID token in javascript , ID token contains user profile information (like the user's name, email, and so forth) , these claims are statements about the user but should not include sensitive information.
If you want to get the user claims you can query claims from HttpContext.User object and pass related claims to client side . Or directly get the user's profile information from Azure AD using Microsoft Graph API .
You can use cookie to store the id token in OnAuthorizationCodeReceivedevent, something like :
context.Response.Cookies.Append("IDToken", context.ProtocolMessage.IdToken, new CookieOptions()
{
Path = "/",
HttpOnly = false,
Secure = true
});
And access the value on server side by :
var idToken= Request.Cookies["IDToken"];
But you should concern the security problem . When used with the HttpOnly cookie flag(=true), are not accessible through JavaScript, and are immune to XSS. You can also set the Secure cookie flag to guarantee the cookie is only sent over HTTPS. However cookies are vulnerable to cross-site request forgery (CSRF) attacks . You can refer to this thread which provides detail discussion about that .
In short , you can store the ID token in cookie . But i would suggest to get the user claims from HttpContext.User object .
Recently I was using the Sign-up and Sign-in template similar this one developed by Vladimir Budilov.
But now, I've been modifying my application to use the hosted UI developed by Amazon. So my application redirects to the hosted UI, all the authentication is made there and they send me the authentication token, more os less as explained in this tutorial.
Summarizing, I call the hosted UI and do login:
https://my_domain/login?response_type=token&client_id=my_client_id&redirect_uri=https://www.example.com
I'm redirected to:
https://www.example.com/#id_token=123456789tokens123456789&expires_in=3600&token_type=Bearer
So, I have now the token_id but I can't get the current user or user parameters from this. Could anyone help me with informations or some directions?
I've tried the methods in Amazon developer guide .
It works well when I was using Vladimir Budilov's template but trying to use the token_id, I'm not succeeding. Thanks in advance for your time and help.
var data = {
UserPoolId : '...', // Your user pool id here
ClientId : '...' // Your client id here
};
var userPool = new AmazonCognitoIdentity.CognitoUserPool(data);
var cognitoUser = userPool.getCurrentUser();
if (cognitoUser != null) {
cognitoUser.getSession(function(err, session) {
if (err) {
alert(err);
return;
}
console.log('session validity: ' + session.isValid());
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId : '...' // your identity pool id here
Logins : {
// Change the key below according to the specific region your user pool is in.
'cognito-idp.<region>.amazonaws.com/<YOUR_USER_POOL_ID>' : session.getIdToken().getJwtToken()
}
});
// Instantiate aws sdk service objects now that the credentials have been updated.
// example: var s3 = new AWS.S3();
});
}
The attributes you configure to be added as claims are already available inside the id_token with base64 encoding (Since its a JWT token).
You can decode the token and access these attributes both at Client Side using Javascript and on Server.
For more info refer the StackOverflow question How to decode JWT tokens in JavaScript.
Note: If you need to trust these attributes for a backend operation, make sure you verify the JWT signature before trusting the attributes.
Here's a specific example of how to parse the callback parameters and set up a user session. This could be initiated in onLoad of your page.
import { CognitoAuth } from 'amazon-cognito-auth-js';
const authData = {
ClientId : '<TODO: add ClientId>', // Your client id here
AppWebDomain : '<TODO: add App Web Domain>',
TokenScopesArray : ['<TODO: add scope array>'], // e.g.['phone', 'email', 'profile','openid', 'aws.cognito.signin.user.admin'],
RedirectUriSignIn : '<TODO: add redirect url when signed in>',
RedirectUriSignOut : '<TODO: add redirect url when signed out>',
IdentityProvider : '<TODO: add identity provider you want to specify>', // e.g. 'Facebook',
UserPoolId : '<TODO: add UserPoolId>', // Your user pool id here
AdvancedSecurityDataCollectionFlag : '<TODO: boolean value indicating whether you want to enable advanced security data collection>', // e.g. true
Storage: '<TODO the storage object>' // OPTIONAL e.g. new CookieStorage(), to use the specified storage provided
};
const auth = new CognitoAuth(authData);
auth.userhandler = {
onSuccess: function(result) {
alert("Sign in success");
showSignedIn(result);
},
onFailure: function(err) {
alert("Error!");
}
};
const curUrl = window.location.href;
auth.parseCognitoWebResponse(curUrl);
Now you're "signed in" as far as the Cognito JS client is concerned, and you can use getCurrentUser(), getSession(), etc. `See "Use case 2" here for more context/details.
My front end application is authenticated using gmail account.
I retrieve id_token after the authentication is successful and send it as Authorization Header as bearer token.
E.g.
http://localhost:4000/api
Authorization Bearer token_id
At nodejs server side, I call the following method to verify the token.
exports.verifyUser = function(req, res, next) {
var GoogleAuth = require('google-auth-library');
var auth = new GoogleAuth();
var client = new auth.OAuth2(config.passport.google.clientID, config.passport.google.clientSecret, config.passport.google.callbackURL);
// check header or url parameters or post parameters for token
var token = "";
var tokenHeader = req.headers["authorization"];
var items = tokenHeader.split(/[ ]+/);
if (items.length > 1 && items[0].trim().toLowerCase() == "bearer") {
token = items[1];
}
if (token) {
var verifyToken = new Promise(function(resolve, reject) {
client.verifyIdToken(
token,
config.passport.google.clientID,
function(e, login) {
console.log(e);
if (login) {
var payload = login.getPayload();
var googleId = payload['sub'];
resolve(googleId);
next();
} else {
reject("invalid token");
}
}
)
}).then(function(googleId) {
res.send(googleId);
}).catch(function(err) {
res.send(err);
})
} else {
res.send("Please pass token");
}
}
When I call the above method, I always get Invalid token response with following error.
Error: No pem found for envelope: {"alg":"RS256","kid":"c1ab5857066442ea01a01601
850770676460a712"}
at OAuth2Client.verifySignedJwtWithCerts (\node_modules\google-auth-libr
ary\lib\auth\oauth2client.js:518:13)
Is this the right approach to verify token?
Do I send the id_token as Authorization bearer? Or is it for authorization only?
How do I send the id_token to the sever side? Thru url, header?
What am I doing wrong?
Any help is highly appreciated.
OAuth2Client.verifyIdToken take an idToken in arguments, from the library source :
/**
* Verify id token is token by checking the certs and audience
* #param {string} idToken ID Token.
* #param {(string|Array.<string>)} audience The audience to verify against the ID Token
* #param {function=} callback Callback supplying GoogleLogin if successful
*/
OAuth2Client.prototype.verifyIdToken = function(idToken, audience, callback)
You have passed the whole header value bearer eyJhbGciOiJSUzI1NiIsImtpZCI6ImMxYWI1OD U3MDY2NDQyZWEwMWEwMTYwMTg1MDc3MDY3NjQ2MGE3MTIifQ so you will have to split the headers value as :
var authorization = req.headers["authorization"];
var items = authorization.split(/[ ]+/);
if (items.length > 1 && items[0].trim() == "Bearer") {
var token = items[1];
console.log(token);
// verify token
}
Is this the right approach to verify token ?
Yes, this is the right way to verify token. For debugging, you can also verify token with the tokeninfo endpoint if you have any doubt or for quick testing :
https://www.googleapis.com/oauth2/v3/tokeninfo?id_token=XYZ123
Do I send the id_token as Authorization bearer? Or is it for
authorization only?
How do I send the id_token to the sever side? Thru
url, header?
You can send JWT token in Authorization header but it could lead to usecase where you have multiple Authorization headers. It's best to URL encode or embed the token in the body. You can check Google example here
Moreover, the following are required by Google :
the token must be sent via HTTPS POST
the token integrity must be verified
To optimize your code, you could also move your Google auth object to your app.js at the root of your app instead of redefining it each time the token should be verified. In app.js :
var app = express();
var GoogleAuth = require('google-auth-library');
var auth = new GoogleAuth();
app.authClient = new auth.OAuth2(config.passport.google.clientID, config.passport.google.clientSecret, config.passport.google.callbackURL);
and in verifyUser call it from req.app.authClient :
req.app.authClient.verifyIdToken(...)
I finally found the answer today.
The Firebase tool will connect the native Google to the third-party login token, and then encapsulate another layer. The token obtained at this time is no longer the original token given to us by Google.
A1:
Original Token: GoogleDesignInAccount Account = Task.getResult(ApiException.class);
Account.getidToken () // This is the original token
B1:
Firebase token: FireBaseUser currentUser = Mauth.getCurrentUser ();
String token = currentUser.getIdToken(false).getResult().getToken();
A2:
Google officially provides a method to verify the token
B2:
Firebase officially provides the authentication token method
We use code names for the four data points above. If you need to verify the validity of tokens in the background, they must correspond to each other, A1 to A2 and B1 to B2. If you use A2 to validate the B1, it will fail
First of all, do not use Id_Token for authorization. It is only for authentication. Use access token for authorization. Use link below to verify token.
https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=${access_token}
In quickblox, the session expires two hours after the last request. So to handle this situation I have used the code
config.on.sessionExpired = function(next,retry){
})
and passed the config in QB.init
config.on.sessionExpired = function(next, retry) {
console.log("============session renewal")
var user = self.get('user')
QB.chat.disconnect()
QB.createSession({ login: user.login, password: user.pass }, function(err, res) {
if (res) {
// save session token
token = res.token;
user.id = res.user_id
self.get('user').token = token
QB.chat.connect({ userId: user.id, password: user.pass }, function(err, roster) {
// Do something
})
}
})
}
QB.init(QBApp.appId, QBApp.authKey, QBApp.authSecret, config);
Is this the right way to renew the session by first disconnecting the chat, then creating a new session first and then connecting the chat back again?
I do not want the client to know that the session has expired in quickblox and they have to refresh the page. The chat is a part of the web portal. It is fine if the quickblox takes 2-3 seconds to create a new session token and then connect to chat. By the time, I can show a loader or some message.
I had tried it without the QB.chat.disconnect() but then it did not work and sent me Unprocessable entity 422 error.
I have same problem, and i found some solution at QuickBlox Docs
https://docs.quickblox.com/docs/js-authentication#session-expiration
QuickBlox JavaScript SDK will automatically renew your current session. There is no need to manually call createSession() method. After login, a session is available for 2 hours. When the session expires, any request method will firstly renew it and then execute itself.
And this example from the official documentation:
var CONFIG = {
on: {
sessionExpired: function(handleResponse, retry) {
// call handleResponse() if you do not want to process a session expiration,
// so an error will be returned to origin request
// handleResponse();
QB.createSession(function(error, session) {
retry(session);
});
}
}
};
QB.init(3477, "ChRnwEJ3WzxH9O4", "AS546kpUQ2tfbvv", config);