I am in the process of migrating a node.js application to Firebase v3.
In v2 I was using FirebaseTokenGenerator to generate custom tokens. It requires an apiToken, which is inconsistent with the way that Firebase v3 works in node, and I see there is now a 'createCustomToken' method on the firebase.auth service so I am assuming that I should now use that.
The issue is that this method appears to accept only 'uid' and 'developerClaims' as parameters, where FirebaseTokenGenerator also accepted an options object which included an 'expires' attribute.
Is there a way to give the token generated by 'createCustomToken' an expiry date?
Update
Reference: https://groups.google.com/forum/#!topic/firebase-talk/Ezy3RDNNRAs
Once they login using the custom token, the Firebase exchanged Id
token is long lived and is automatically refreshed. You don't need to
mint a new custom token on each request. You can verify the Firebase
Id token using the backend server libraries and as long as it is
valid, you don't to sign in the user again.
So it looks like the generated token is temporary and used to retrieve an id token (internally) with
FIRAuth.auth()?.signInWithCustomToken(customToken)
From then on the client should be good.
With Firebase 3.0.4 Currently No.
From the nodejs module source code it looks like the jwt expiresIn is set at 1 hour. This is unacceptable for mobile app users (as long as they're logged in their key should be fine). Hope this is fixed asap since it blocks us from upgrading our sdk
FirebaseTokenGenerator.prototype.createCustomToken = function(uid, developerClaims) {
if (typeof uid !== 'string' || uid === '') {
throw new Error('First argument to createCustomToken() must be a non-empty string uid');
} else if (uid.length > 128) {
throw new Error('First argument to createCustomToken() must a uid with less than or equal to 128 characters');
} else if (typeof developerClaims !== 'undefined' && (typeof developerClaims !== 'object' || developerClaims === null || developerClaims instanceof Array)) {
throw new Error('Optional second argument to createCustomToken() must be an object containing the developer claims');
}
var jwtPayload = {};
if (typeof developerClaims !== 'undefined') {
jwtPayload.claims = {};
for (var key in developerClaims) {
/* istanbul ignore else */
if (developerClaims.hasOwnProperty(key)) {
if (BLACKLISTED_CLAIMS.indexOf(key) !== -1) {
throw new Error('Developer claim "' + key + '" is reserved and cannot be specified');
}
jwtPayload.claims[key] = developerClaims[key];
}
}
}
jwtPayload.uid = uid;
return jwt.sign(jwtPayload, this.serviceAccount.private_key, {
audience: FIREBASE_AUDIENCE,
expiresIn: ONE_HOUR_IN_SECONDS,
issuer: this.serviceAccount.client_email,
subject: this.serviceAccount.client_email,
algorithm: ALGORITHM
});
};
Update the below won't work due to this comment
"exp The time, in seconds, at which the token expires. It can be at a maximum 3600 seconds later than iat."
Firebase token max life span is 1 hour.
The solution appears to be generating our own token
Use a JWT library
You can create a custom token suitable for authenticating with Firebase by using any JWT creation library. Create a JWT that includes the following claims and is signed using RS256.
JWT claims
iss Your project's service account email address
sub Your project's service account email address
aud https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit
iat The current time, in seconds
exp The time, in seconds, at which the token expires. It can be at a maximum 3600 seconds later than iat.
uid The unique identifier of the signed-in user (must be a string, between 1-36 characters long)
claims (optional) Custom claims to include in the Security Rules auth variable.
An example of a token generating function that should meet the above criteria:
var ALGORITHM = 'RS256';
// List of blacklisted claims which cannot be provided when creating a custom token
var BLACKLISTED_CLAIMS = [
'acr', 'amr', 'at_hash', 'aud', 'auth_time', 'azp', 'cnf', 'c_hash', 'exp', 'iat', 'iss', 'jti',
'nbf', 'nonce'
];
var FIREBASE_AUDIENCE = 'https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit';
function generateFirebaseToken(serviceAccount, uid, expiresIn, developerClaims) {
var jwtPayload = {};
if (typeof developerClaims !== 'undefined') {
jwtPayload.claims = {};
for (var key in developerClaims) {
if (developerClaims.hasOwnProperty(key)) {
if (BLACKLISTED_CLAIMS.indexOf(key) !== -1) {
throw new Error('Developer claim "' + key + '" is reserved and cannot be specified');
}
jwtPayload.claims[key] = developerClaims[key];
}
}
}
jwtPayload.uid = uid;
return jwt.sign(jwtPayload, serviceAccount.private_key, {
audience: FIREBASE_AUDIENCE,
expiresIn: expiresIn,
issuer: serviceAccount.client_email,
subject: serviceAccount.client_email,
algorithm: ALGORITHM
});
}
Reference: firebase docs
Related
I am using Firebase auth email accounts to sign up users to a site.
What I have noticed lately is the below cases.
Users sign up using a valid email address and then never verify the
email address
Users attempt to sign up using a fake email address
For the first case we can search all accounts that have not been verified within a time span and delete them.
admin.auth().getUser(uid).then(user => {
const creationTime = user.metadata.creationTime
const isVerified = user.emailVerified
const lastSignInTime = user.lastSignInTime
if(!isVerified){
// Process and delete unverified accounts after x days
...
}
})
How can we handle accounts where the email address is fake or misspelled? I am not seeing any property on the firebase.User object that indicates an invalid email address. We do however receive a mail delivery failure message for each user that has signed up using a invalid email address - this is not enough to automatically purge fake / invalid accounts.
What are best practices on preventing fake signups?
Kind regards /K
You can't stop someone from using any string that looks like an email address, and the system doesn't have a way of telling you that the verification email was successfully sent.
The usual way to deal with this is to create some database record for each user account that tracks their validation status. You can query the database to find out which users have not validated after some amount of time. Your app should be sending your backend ID tokens from the user that can be used to check if they are validated, and if so, update the database to show that it happened.
So this is the code I came up with to purge unverified accounts.
May not be the most elegant solution, but works.
exports.scheduledUserCleanup = functions
.region('europe-west1')
.pubsub
.schedule('0 3 * * *')
.timeZone('Europe/Stockholm')
.onRun(async (event) => {
const today = moment()
const inactivityThresholdDays = 7 //Get purge threshold days
let myPromises = [] //Collect all promises to carry out
//Search for users that have NOT validated email
database.ref('user-signups').once('value', (usersSnapshots) => {
usersSnapshots.forEach((snapshot) => {
const uid = snapshot.key
// Get user from firebase auth
admin.auth().getUser(uid)
.then((firebaseAuthUser) => {
const creationTimeStr = firebaseAuthUser.metadata.creationTime
const isVerified = firebaseAuthUser.emailVerified
const lastSignInTimeStr = firebaseAuthUser.metadata.lastSignInTime
const neverSignedIn = (creationTimeStr === lastSignInTimeStr) ? true : false
if(!isVerified && neverSignedIn){
// Process and delete unverified accounts after 7 days
const creationTime = moment(creationTimeStr)
const daysSinceCreation = today.diff(creationTime, 'days')
if(daysSinceCreation > inactivityThresholdDays){
console.log('Remove user from db and Firebase auth', uid)
myPromises.push( admin.auth().deleteUser(firebaseAuthUser.uid) )
myPromises.push( database.ref(`user-signups/${uid}`).remove() )
}else{
console.log(`Keep for ${inactivityThresholdDays} days before deleting`, uid)
}
}
return true
})
.catch((error) => {
// Remove if not found in Firebase Auth
const notFoundInFirebaseAuth = (error.code) ? error.code === 'auth/user-not-found' : false
if(notFoundInFirebaseAuth){
console.log('Remove user from db', uid)
myPromises.push( database.ref(`user-signups/${uid}`).remove() )
}
return false
})
})
})
// Execute promises
return Promise.all(myPromises)
.then(() => Promise.resolve(true))
.catch((err) => {
console.error('Error', err)
return Promise.reject(err)
})
})
I am trying to upload signature for a user using eSign SDK. I have referred this link for creating a signature for user. Below is my C# code:
var usersList = usersApi.List(Constants.accountId);
ApiClient apiClient1 = new ApiClient(Constants.basePath);
apiClient1.Configuration.AddDefaultHeader("Authorization", "Bearer " + Constants.userAccessToken);
UsersApi usersApi1 = new UsersApi(apiClient1.Configuration);
if (usersList != null && usersList.Users != null && usersList.Users.Any())
{
var activeUser = usersList.Users.FirstOrDefault(x => x.Email == data["UserEmail"] && x.UserStatus.Equals("active", comparisonType: StringComparison.CurrentCultureIgnoreCase));
if (activeUser != null)
{
UserSignature userSignature = new UserSignature()
{
IsDefault = "true",
SignatureInitials = "TU",
SignatureName = "Test User",
ImageBase64 = Convert.ToBase64String(ReadContent(Constants.eSignName))
};
List<UserSignature> userSignatures = new List<UserSignature>() { userSignature };
UserSignaturesInformation userSignaturesInformation = new UserSignaturesInformation()
{
UserSignatures = userSignatures
};
//Create Signature
var signResult = usersApi1.CreateSignatures(Constants.accountId, activeUser.UserId, userSignaturesInformation);
}
}
After this call, the signature gets created. However, the user's signature image(passed in ImageBase64 parameter) is not added to the created signature . The access token I have used here is from DocuSign Token generator tool from User profile. Need help in figuring out what's wrong with this request.
In order to take action on behalf of a different user (in your case, you want to generate and upload a signature image for a different user) you have to impersonate them when you authenticate using JWT. You have to have their userID (a GUID) as part of your API call and the user must consent to the app making call on their behalf at least once.
Here is my issue:
We integrated docusign in our application, server side with nodejs using this tutorial https://github.com/docusign/docusign-node-client ("OAuth JSON Web Token (JWT) Grant" section)
We have done the "Go Live Process": our application is registered in our production account
We have replaced the test config to the production config.
When we try to create an envelope, we get the following error:
PARTNER_AUTHENTICATION_FAILED: The specified Integrator Key was not found or is disabled. Invalid account specified for user
What am I doing wrong ?
async function docusignInit() {
var options;
var env = [40077,50077].indexOf(config.main.port) != -1 ? 'test' :'prod';
if (env == "test") {
options = {
basePath: restApi.BasePath.DEMO,
oAuthBasePath: oAuth.BasePath.DEMO
}
} else {
options = {
oAuthBasePath: "account.docusign.com",
// We called https://account.docusign.com/oauth/userinfo to found the uri
basePath:"https://eu.docusign.net/restapi/"
}
}
// in production, We must do
// var apiClient = new docusign.ApiClient(options.basePath);
// Otherwise, we get "Error: getaddrinfo ENOTFOUND undefined undefined:443"
var apiClient = new docusign.ApiClient(options.basePath);
var privateKeyFile = fs.readFileSync(`./server/docusign/keys/${env}/private.PEM`);
var res = await apiClient.requestJWTUserToken(config.docusign.integratorKey, config.docusign.userName, [oAuth.Scope.IMPERSONATION, oAuth.Scope.SIGNATURE], privateKeyFile, 3600)
var token = res.body.access_token;
apiClient.addDefaultHeader('Authorization', 'Bearer ' + token);
docusign.Configuration.default.setDefaultApiClient(apiClient);
await sendDocusign({
userId: 1,
firstName: 'foor',
lastName: 'bar',
email:'foo#bar;'
})
}
async function sendDocusign(role) {
var envDef = new docusign.EnvelopeDefinition();
envDef.emailSubject = 'Please signe this';
envDef.templateId = config.docusign.templateId;
var role = new docusign.TemplateRole();
role.roleName = "roleName";
role.clientUserId = role.userId;
role.name = role.firstName + " " + role.lastName;
role.email = role.email;
envDef.allowReassign = false;
envDef.templateRoles = [role];
envDef.status = 'sent';
var envelopesApi = new docusign.EnvelopesApi();
return await envelopesApi.createEnvelope(config.docusign.userAccountId, {
'envelopeDefinition': envDef
})
}
As you are able to generate AccesToken properly in PROD with PROD RSA KeyPair, so please check the endpoint which you using to make API calls to create an envelope. In demo it is always demo.docusign.net but in PROD it will be a different value depending on where you PROD account exists in the DocuSign data center. For instance if your PROD account is in NA1, then hostname will be will be www.docusign.net; if it is NA2 then hostname will be na2.docusign.net etc.
So it is recommended to make a /userinfo API call with the Access token to know the baseURI to make calls related to envelope. To get the base URI, call the /oauth/userinfo endpoint, supplying your application’s access token as a header.
For the developer sandbox environment, the URI is
https://account-d.docusign.com/oauth/userinfo
For the production environment, the URI is
https://account.docusign.com/oauth/userinfo
Documentation related to /userinfo API call is available here. Once you know you BaseURI then append this baseURI with envelopes related endpoint like below:
{base_uri} + "/restapi/v2.1/accounts/" + {account_id}
considering your error seems that you're missing the integratorKey or you're writing it in the wrontg way. According to that LINK is possible that you miss the brackets inside the intregrator key?
The integrator key must be placed in front of the user ID that is in
the Username node of the UsernameToken. The integrator key must be
wrapped with brackets, “[ and ]”.
An example of the api in the above documentation:
<soap:Header>
<wsa:Action>http://www.docusign.net/API/3.0/GetRecipientEsignList</wsa:Action>
<wsa:MessageID>uuid:3f9d7626-c088-43b4-b579-2bd5e8026b17</wsa:MessageID>
<wsa:ReplyTo>
<wsa:Address>http://schemas.xmlsoap.org/ws/2004/03/addressing/role/anonymous</wsa:Address>
</wsa:ReplyTo>
<wsa:To>http://demo.docusign.net/api/3.0/api.asmx</wsa:To>
<wsse:Security soap:mustUnderstand="1">
<wsu:Timestamp wsu:Id="Timestamp-8838aa24-9759-4f85-8bf2-26539e14f750">
<wsu:Created>2006-04-14T14:29:23Z</wsu:Created>
<wsu:Expires>2006-04-14T14:34:23Z</wsu:Expires>
</wsu:Timestamp>
<wsse:UsernameToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="SecurityToken-7c7b695e-cef7-463b-b05a-9e133ea43c41">
<wsse:Username>[Integrator Key Here]2988541c-4ec7-4245-b520-f2d324062ca3</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">password</wsse:Password>
<wsse:Nonce>SjlScsL5q3cC1CDWrcMx3A==</wsse:Nonce>
<wsu:Created>2006-04-14T14:29:23Z</wsu:Created>
</wsse:UsernameToken>
</wsse:Security>
</soap:Header>
I am having trouble getting my token and account sid to work. i set them as constants in the file and the following error appears
Error:
throw 'Client requires an Account SID and Auth Token set explicitly ' +
^
Client requires an Account SID and Auth Token set explicitly or via the TWILIO_ACCOUNT_SID and TWILIO_AUTH_TOKEN environment variables
const sid = xxxxxxxxxxx'
const tkn = 'xxxxxxxxxxx'
function Client(sid, tkn, host, api_version, timeout) {
//Required client config
if (!sid || !tkn) {
if (process.env.TWILIO_ACCOUNT_SID && process.env.TWILIO_AUTH_TOKEN) {
this.accountSid = process.env.TWILIO_ACCOUNT_SID;
this.authToken = process.env.TWILIO_AUTH_TOKEN;
}
else {
throw 'Client requires an Account SID and Auth Token set explicitly ' +
'or via the TWILIO_ACCOUNT_SID and TWILIO_AUTH_TOKEN environment variables';
}
}
else {
//if auth token/SID passed in manually, trim spaces
this.accountSid = sid.replace(/ /g, '');
this.authToken = tkn.replace(/ /g, '');
}
//Optional client config
this.host = host || defaultHost;
this.apiVersion = api_version || defaultApiVersion;
this.timeout = timeout || 31000; // request timeout in milliseconds
}
Twilio developer evangelist here.
You are setting sid and tkn as constants outside the Client function, however you also name the first two arguments passed into the function as sid and tkn. Within the scope of Client that means that sid and tkn are whatever you pass as arguments to the function.
My guess is that you are calling the Client function without any arguments and that leads to this error.
Rather than setting the sid and tkn in the file with the Client function, I recommend you pass them in as arguments when you call it.
const client = new Client(sid, tkn);
Let me know if that helps at all.
I am having some issue to make work 2 auth provider at the same time for servicestack.
I am using the : JWT Tokens - Allowing users to authenticate with JWT Tokens I am my users get authenticate fine.
Still Now I would like to use the API Keys - Allowing users to authenticate with API Keys for a few external 3rd Parties user access.
Still when I Configure both my users allready authenticate by JWT Tokens doesnt work anymore.
Here is my configuration AuthProvider configuration :
IAuthProvider[] providers = new IAuthProvider[]
{
new JwtAuthProviderReader(this.AppSettings)
{
HashAlgorithm = "RS256",
PrivateKeyXml = this.AppSettings.GetString("TokenPrivateKeyXml"),
PublicKeyXml = this.AppSettings.GetString("TokenPublicKeyXml"),
RequireSecureConnection = this.AppSettings.Get<bool>("TokenUseHttps"),
EncryptPayload = this.AppSettings.Get<bool>("TokenEncryptPayload"),
PopulateSessionFilter = (session, obj, req) =>
{
ApplicationUserSession customSession = session as ApplicationUserSession;
if (customSession != null)
{
customSession.TimeZoneName = obj["TimeZoneName"];
customSession.Type = (FbEnums.UserType) (obj["UserType"].ToInt());
if (Guid.TryParse(obj["RefIdGuid"], out Guid result))
{
customSession.RefIdGuid = result;
}
}
},
},
new ApiKeyAuthProvider(AppSettings)
{
RequireSecureConnection = false
}
};
I am genereting fine the token with JwtAuth. Still It look like servicestack is not accepting my token as a valid session, because now whenever I do :
var session = httpReq.GetSession();
session.IsAuthenticated --> is always FALSE
If my remove ApiKeyAuthProvider from the configuration, token from JwtAuth working fine again.
How do I make both provider works together and tell servicestack tham some users will use JwtAuth and others ApiKeyAuth ?
You need to call a Service that requires Authentication, e.g. has the [Authenticate] attribute in order to trigger pre-Authentication for the IAuthWithRequest providers like JWT and API Key AuthProviders.