I implemented two factor authentication but by following this tutorial
https://learn.microsoft.com/en-us/aspnet/identity/overview/features-api/two-factor-authentication-using-sms-and-email-with-aspnet-identity
I want to make the code expire after single use.
Right now, user receives the same code during the expiration time (which is set to 5 minutes) completes. Is there a way to make the code single use? I couldn't find anything on this subject.
There is a note in the tutorial that you linked to that says:
The 2FA codes are generated using Time-based One-time Password Algorithm and codes are valid for six minutes. If you take more than six minutes to enter the code, you'll get an Invalid code error message.
So, using this method, you cannot make the code expire after user.
You could, as an addition, keep a store of codes that have been used and check against that store before validating the code. You could allow the codes to expire out of that store after 6 minutes, which is their natural expiry time, but in the meantime use them to reject a second authentication.
Alternatively, you can choose to avoid the TOTP method and generate a random code that you store against your user before you send the SMS or email. Then you can check against that code when the user authenticates with it and delete or invalidate the code at that point. Using TOTP means that you could extend this 2FA to use an authenticator app based flow for the authentication too, which is more secure than SMS or email.
AspNetIdentity does not automatically invalidate used second factor codes, a code is always valid for a six minute window, but there is a workaround for this.
One of the inputs to the token generator is the SecurityStamp, which is stored as part of the user account. Token providers that extend the TotpSecurityStampBasedTokenProvider, like for example the EmailTokenProvider, will use the security stamp when they generate and validate a second factor code.
Thus, you can invalidate all issued tokens by changing the security stamp by calling UserManager.UpdateSecurityStampAsync(userId) after a successful two factor authentication.
There is a side effect that may not be desirable, being that other sessions will get logged out when the security stamp changes.
In the ApplicationSignInManager class, you can override TwoFactorSignInAsync and make the call there:
(Note: This is taken from AspNetIdentity, if you are using a different package, make sure to take TwoFactorSignInAsync from that instead and modify it accordingly.)
public override async Task<SignInStatus> TwoFactorSignInAsync(string provider, string code, bool isPersistent, bool rememberBrowser)
{
var userId = await GetVerifiedUserIdAsync().WithCurrentCulture();
if (userId == null)
{
return SignInStatus.Failure;
}
var user = await UserManager.FindByIdAsync(userId).WithCurrentCulture();
if (user == null)
{
return SignInStatus.Failure;
}
if (await UserManager.IsLockedOutAsync(user.Id).WithCurrentCulture())
{
return SignInStatus.LockedOut;
}
if (await UserManager.VerifyTwoFactorTokenAsync(user.Id, provider, code).WithCurrentCulture())
{
// When token is verified correctly, clear the access failed count used for lockout
await UserManager.ResetAccessFailedCountAsync(user.Id).WithCurrentCulture();
// Update the security stamp in order to invalidate all issued two factor tokens.
await UserManager.UpdateSecurityStampAsync(user.Id);
await SignInAsync(user, isPersistent, rememberBrowser).WithCurrentCulture();
return SignInStatus.Success;
}
// If the token is incorrect, record the failure which also may cause the user to be locked out
await UserManager.AccessFailedAsync(user.Id).WithCurrentCulture();
return SignInStatus.Failure;
}
If you want only the latest issued code to be valid, you should make the call to UpdateSecurityStampAsync also before any new code is generated.
Related
I am developing a Google Workspace Addon (standalone script) which will make REST API calls to external service and for that purpose it needs to provide an API key.
I request the API key input from a user and then store it in PropertiesService in the following way:
function onSheets(e) {
const userProperties = PropertiesService.getUserProperties();
const saved_api_key = userProperties.getProperty('api_key');
const api_key: string = saved_api_key ? saved_api_key : "";
const builder = CardService.newCardBuilder();
const apiKeyInput = CardService.newTextInput().setTitle('API Key')
.setFieldName('api_key')
.setHint('Enter your API Key')
.setValue(api_key);
const saveApiKey = CardService.newAction().setFunctionName('saveApiKeyFn');
const button = CardService.newTextButton().setText('Save').setOnClickAction(saveApiKey);
const optionsSection = CardService.newCardSection()
.addWidget(apiKeyInput)
.addWidget(button)
builder.addSection(optionsSection);
return builder.build();
}
function saveApiKeyFn(e) {
const api_key = e.formInput.api_key;
const userProperties = PropertiesService.getUserProperties();
userProperties.setProperty('api_key', api_key);
return CardService.newActionResponseBuilder()
.setNotification(CardService.newNotification()
.setText("API Key saved"))
.build();
}
Since userProperties are scoped to a current user it seems fine. But I have serveral problems with this solution:
Is this really safe? I mean it is stored in plain text so maybe there are ways to retrive it by other mailcious user?
The idea that by mistake I would use getScriptProperties() and thus leak one user's API key to all other users gives me nightmares. It is highly sensitive API key. It would cost a user tons of money if abused.
I read that some user's suggest https://cloud.google.com/secret-manager but I am not sure it's fit for this particular scenario. It would require one more external API call. It is not free. And lastly from what I underestand I would be sort of an owner of all of these secrets since I will be the owner of the Google Cloud project in which this API runs.
All I want is for the users to be able to store their keys safely, so that no one else including me can never access them.
What would you suggest? Thanks!
Is this really safe? I mean it is stored in plain text so maybe there are ways to retrive it by other mailcious user?
Security is relative. There's no such thing as absolute secrecy. Here are some attack scenarios:
Google employees or support may have unrestricted access
If a particular user installed a trigger, that trigger runs as that user and other users, if they can trigger the script and have edit access to the script, will be able to access the keys. A common scenario would be a installed edit trigger in a sheet. User B can access user A, if he can make a edit as well as edit the script. As mentioned in the comments by doubleunary, this is less of a problem in a published add on, as the source code is not accessible or editable.
Encrypting keys is a possibility. But, where would you store the decrypting key? You could ask every user to have a custom password for decrypting the key. But how many times are you going to make a API call? Would they have to enter the key every time? At what point does convenience overtake the need for secrecy?
The idea that by mistake I would use getScriptProperties() and thus leak one user's API key to all other users gives me nightmares. It is highly sensitive API key. It would cost a user tons of money if abused.
That is a possibility, but one that's easily avoidable by careful code review by yourself and your peers.
Those are the scenarios I could think of.
Related:
Securely Storing API Secrets used in Google Apps Script - Published Library
I've been having this issue for a while now. I'm trying to add a Sign in through Steam button, which upon login, not only retrieves the user's ID, but also validates the signature. Steam uses OpenID 2.0.
I have followed the documentation here. I have followed these steps carefully, spending the better part of my day on trying to figure this out. My code is this:
let s = data['openid.signed'].split(',');
let x = Buffer.from(s.map(x => `${x}:${data['openid.' + x]}`).join('\n') + '\n', 'utf8');
let c = crypto.createHash('sha1').update(x).digest('base64');
console.log(x.toString('utf8')); // This is the key:value string
console.log(c); // This is the final result; the generated signature
Where data is the response given from the OpenID provider.
Logging x (key:value pair string) gives the expected output of:
signed:signed,op_endpoint,claimed_id,identity,return_to,response_nonce,assoc_handle
op_endpoint:https://steamcommunity.com/openid/login
claimed_id:https://steamcommunity.com/openid/id/765611981[1234567]
identity:https://steamcommunity.com/openid/id/765611981[1234567]
return_to:http://127.0.0.1:8000/resolve
response_nonce:2018-12-01T17:53: [some_hash]=
assoc_handle:1234567890
However, my generated hash c does not match the given signature, openid.sig. Note that I use a \n at the end of the above key:value pair string, as that is how I interpreted the documentation.
Note. The reason why I need authentication is that I want to connect the Steam account to an account on my website, and being logged in via Steam gives you full access to your account on my website, meaning that it's of utter importance that a user cannot simply enter another users id and get access to their account (replay attack). Because of this, I need to somehow validate the signature.
I have never worked with OpenID before, so please excuse any foolish mistakes of mine. I highly recommend reading the documentation that is linked above, so that you can verify what I am doing is right.
Kinds regards,
Initial Request
Make your Steam login button link to
https://steamcommunity.com/openid/login?openid.ns=http://specs.openid.net/auth/2.0&openid.claimed_id=http://specs.openid.net/auth/2.0/identifier_select&openid.identity=http://specs.openid.net/auth/2.0/identifier_select&openid.return_to=https://mywebsite.com&openid.realm=https://mywebsite.com&openid.mode=checkid_setup
and replace the openid.return_to and openid.realm query string parameters.
openid.return_to: This is the URL that Steam will redirect to upon successful login with appended query string parameters.
openid.realm The URL Steam will ask the user to trust. It will appear as a message like this when the user is on the Steam login page: Sign into {openid.realm} using your Steam account. Note that {openid.realm} is not affiliated with Steam or Valve.
Handling the response
Upon successful login, Steam will redirect to a URL like
https://mywebsite.com/?openid.ns=http://specs.openid.net/auth/2.0&openid.mode=id_res&openid.op_endpoint=https://steamcommunity.com/openid/login&openid.claimed_id=https://steamcommunity.com/openid/id/76561198002516729&openid.identity=https://steamcommunity.com/openid/id/76561198002516729&openid.return_to=https:/%mywebsite.com&openid.response_nonce=2020-08-27T04:44:16Zs4DPZce8qc+iPCe8JgQKB0BiIDI=&openid.assoc_handle=1234567890&openid.signed=signed,op_endpoint,claimed_id,identity,return_to,response_nonce,assoc_handle&openid.sig=W0u5DRbtHE1GG0ZKXjerUZDUGmc=
To verify the user, make a call from your backend to https://steamcommunity.com/openid/login copying every query string parameter from that response with one exception: replace &openid.mode=id_res with &openid.mode=check_authentication. So the final call will be to this URL:
https://steamcommunity.com/openid/login?openid.ns=http://specs.openid.net/auth/2.0&openid.mode=check_authentication&openid.op_endpoint=https://steamcommunity.com/openid/login&openid.claimed_id=https://steamcommunity.com/openid/id/76561198002516729&openid.identity=https://steamcommunity.com/openid/id/76561198002516729&openid.return_to=https://mywebsite.com&openid.response_nonce=2020-08-28T04:44:16Zs4DPZce8qc+iPCe8JgQKB0BiIDI=&openid.assoc_handle=1234567890&openid.signed=signed,op_endpoint,claimed_id,identity,return_to,response_nonce,assoc_handle&openid.sig=W0u5DRbtHE1GG0ZKXjerUZDUGmc=
Steam will return a text/plain response like this:
ns:http://specs.openid.net/auth/2.0
is_valid:true
If true the user is valid, false invalid. Note this call will only return true once and subsequent calls with the same parameters will always return false. From here, you can decide how to maintain the user being logged in (such as creating a unique cookie) and return a redirect response to something like your site's homepage, last page before they clicked the Steam login button, or user detail page, etc...
Is there a stripe API call that we can use to create a user if they don't exist, and retrieve the new user?
say we do this:
export const createCustomer = function (email: string) {
return stripe.customers.create({email});
};
even if the user with that email already exists, it will always create a new customer id. Is there a method that will create a user only if the user email does not exist in stripe?
I just want to avoid a race condition where more than one stripe.customers.create({email}) calls might happen in the same timeframe. For example, we check to see if customer.id exists, and does not, two different server requests could attempt to create a new customer.
Here is the race condition:
const email = 'foo#example.com';
Promise.all([
stripe.customers.retrieve(email).then(function(user){
if(!user){
return stripe.customers.create(email);
}
},
stripe.customers.retrieve(email).then(function(user){
if(!user){
return stripe.customers.create(email);
}
}
])
obviously the race condition is more likely to happen in two different processes or two different server requests, than the same server request, but you get the idea.
No, there is no inbuilt way to do this in Stripe. Stripe does not require that a customer's email address be unique, so you would have to validate it on your side. You can either track your users in your own database and avoid duplicates that way, or you can check with the Stripe API if customers already exist for the given email:
let email = "test#example.com";
let existingCustomers = await stripe.customers.list({email : email});
if(existingCustomers.data.length){
// don't create customer
}else{
let customer = await stripe.customers.create({
email : email
});
}
Indeed it can be solved by validating stripe's customer data retrieval result against stored db.
And then call another API to create afterward.
However for simplicity sake, i agree with #user7898461 & would vouch for retrieveOrCreate customer api :)
As karllekko's comment mentions, Idempotent Keys won't work here because they only last 24 hours.
email isn't a unique field in Stripe; if you want to implement this in your application, you'll need to handle that within your application - i.e., you'll need to store [ email -> Customer ID ]s and do a lookup there to decide if you should create or not.
Assuming you have a user object in your application, then this logic would be better located there anyways, as you'd also want to do this as part of that - and in that case, every user would only have one Stripe Customer, so this would be solved elsewhere.
If your use case is like you don't want to create a customer with the same email twice.
You can use the concept of stripe idempotent request. I used it to avoid duplicate charges for the same order.
You can use customer email as an idempotent key. Stripe handles this at their end. the two request with same idempotent key won't get processed twice.
Also if you want to restrict it for a timeframe the create an idempotent key using customer email and that time frame. It will work.
The API supports idempotency for safely retrying requests without
accidentally performing the same operation twice. For example, if a
request to create a charge fails due to a network connection error,
you can retry the request with the same idempotency key to guarantee
that only a single charge is created.
You can read more about this here. I hope this helps
I need help for creating the REST endpoints. There are couple of activities :
To change the email there are 3 URL requests required:
/changeemail : Here one time password (OTP) is sent to the user's mobile
/users/email : the user sends the one time password from previous step and system sends the email to the new user to click on the email activate link
/activateemail : user clicks on the link in the new email inbox and server updates the new email
To change password :
/users/password (PATCH) : user submits old password and new password and system accordingly updates the new password
Similarly, there are other endpoints to change profile (field include bday, firstname and last name)
after reading online I believe my system as only users as the resource --> so to update the attributes I was thinking of using a single PATCH for change email and change password and along with that something like operation field so the above two features will look like :
For changing email :
operation : 'sendOTPForEmailChange'
operation : 'sendEmailActivationLink'
operation : 'activateEmail'
For changing password :
operation : 'changePassword'
and I will have only one endpoint for all the above operations that is (in nodejs) :
app.patch('/users', function (req, res) {
// depending upon the operation I delegate it to the respective method
if (req.body.operation === 'sendOTPForEmailChange') {
callMethodA();
} else if (req.body.operation === 'sendEmailActivationLink') {
callMethodB();
} else if (req.body.operation === 'activateEmail') {
callMethodC();
} else if (req.body.operation === 'changePassword') {
callMethodC();
} else sendReplyError();
});
Does this sound a good idea ? If not, someone can help me form the endpoints for changeemail and changepassword.
Answer :
I finally settled for using PATCH with operation field in the HTTP Request Body to indicate what operation has to be performed.
Since I was only modifying a single field of the resource I used the PATCH method.
Also, I wanted to avoid using Verbs in the URI so using 'operation' field looked better.
Some references I used in making this decision :
Wilts answer link here
Mark Nottingham' blog link article
and finally JSON MERGE PATCH link RFC
You should make the links that define the particular resource, avoid using PATCH and adding all the logic in one link keep things simple and use separation of concern in the API
like this
1- /users/otp with HTTP Verb: GET -> to get OTP for any perpose
2- /users/password/otp with HTTP Verb: POST -> to verify OTP for password and sending link via email
3- /users/activate with HTTP Verb: POST to activate the user
4- /users/password with HTTP Verb: PUT to update users password
Hashing Security is a must read, IMHO, should you ever want to implement your own user account system.
Two-factor identification should always be considered, at least as an opt-in feature. How would you integrate it into your login scheme ?
What about identity federation ? Can your user leverage their social accounts to use your app ?
A quick look at Google yielded this and this, as well as this.
Unless you have an excellent reason to do it yourself, I'd spend time integrating a solution that is backed by a strong community for the utility aspects of the project, and focus my time on implementing the business value for your customers.
NB: my text was too long for the comments
Mostly agree with Ghulam's reply, separation of concerns is key. I suggest slightly different endpoints as following:
1. POST /users/otp -> as we are creating a new OTP which should be returned with 200 response.
2. POST /users/email -> to link new email, request to include OTP for verification.
3. PUT /users/email -> to activate the email.
4. PUT /users/password -> to update users password.
For our new project we want to leverage as much of the asp.net mvc 5 as we can. This includes making use of the AspNet.Identity toolset for our user administration.
We are using the following version(s):
"Microsoft.AspNet.Identity.Core" version="2.2.1" targetFramework="net46"
"Microsoft.AspNet.Identity.EntityFramework" version="2.2.1" targetFramework="net46"
In our previous roll-your-own applications we made sure that reset links can be used only once, and that they expire within a day or so.
Does does AspNet.Identiy support something similar? I could not find it in the documentation.
To pass our security checks the link should at least expire.
How to make this happen?
to control the lifetime of the token, go to IdentityConfig.cs, next, and the end of the Create function, within the last if related to dataProtectionProvider you can set the time, look:
if (dataProtectionProvider != null)
{
manager.UserTokenProvider =
new DataProtectorTokenProvider<ApplicationUser>(
dataProtectionProvider.Create("ASP.NET Identity")){
**TokenLifespan = TimeSpan.FromMinutes(1)**
};
}
Use the TokenLifespan = TimeSpan.FromMinutes(1) (or the time that you need)
ASP.NET Identity by default generates reset tokens based on existing user properties. This means that when those properties change, the reset token is automatically invalidated. This will meet your one time use requirement (when they use the token and reset their password, the token will no longer be valid).
Reset token expiration can be set when you assign an IUserTokenProvider to the UserTokenProvider property of your UserManager.
A good example of IUserTokenProvider is DataProtectorTokenProvider found in the Microsoft.AspNet.Identity.Owin package. This class uses the previously mentioned security stamp based tokens and allows for expiration times to be set using the TokenLifespan property. For info on how to implement this check out this answer.
By default the token is valid for one day. However, you can change that by customizing the Create method in App_Start\IdentityConfig.cs file
if (dataProtectionProvider != null)
{
manager.UserTokenProvider =
new DataProtectorTokenProvider<ApplicationUser>
(dataProtectionProvider.Create("ASP.NET Identity"))
{
TokenLifespan = TimeSpan.FromHours(3)
};
}
With the code above, the forgotten password and the email confirmation tokens will expire in 3 hours.
Do make sure that your application has a way for users to regenerate token. I had added a Resend Link button. It links to the function that regenerates the token and sends link to the user.
You will more details here: http://www.asp.net/identity/overview/features-api/account-confirmation-and-password-recovery-with-aspnet-identity