I'm building a SaaS application using the MEAN stack and have a few questions regarding how best to secure registration forms. Express.js is what I'm using to generate the endpoints (via angular-fullstack)
I have "tenants" that register using a registration form (name, email, password, etc.). This REST API is currently unsecured (POST /tenants).
My questions are:
Should I somehow secure this POST? If so, how?
If I'm not to secure the POST /tenants endpoint, how do I avoid someone writing a script to just create a whole bunch of tenants and attack my application?
I want to use some sort of confirmation email, but is that good practice when registering a new tenant?
I'd love to get some feedback here on how best to proceed.
Thanks!
reCAPTCHA is the solution I chose to solve this same problem.
Quoting from Google's developer site on reCAPTCHA an overview of integrating reCAPTCHA into a site follow. It's worth noting Google's reCAPTCHA codelab also includes an example for Java.
Overview
To start using reCAPTCHA, you need to sign up for an API key pair
for your site. The key pair consists of a site key and secret key. The
site key is used to invoke reCAPTCHA service on your site or mobile
application. The secret key authorizes communication between your
application backend and the reCAPTCHA server to verify the user's
response. The secret key needs to be kept safe for security
purposes.
First, choose the type of reCAPTCHA and then fill in authorized
domains or package names. After you accept our terms of service,
you can click Register button to get new API key pair.
Now please take the following steps to add reCAPTCHA to your site or
mobile application:
Choose the client side integration:
reCAPTCHA v3
reCAPTCHA v2
Checkbox
Invisible
Android
Verifying the user's response
And being a Python fan, here's an example I followed to implement this solution in Django (ref: Google's codelab example):
1. Getting set up
Install and verify web server
This codelab can either be run locally or through the gcloud shell in
Google Cloud Platform. To get started with the gcloud shell go to
https://console.cloud.google.com/appengine/start.
Download the Code
Clone all the code for this codelab:
git clone https://github.com/googlecodelabs/recaptcha-codelab.git
For Python:
cd recaptcha-codelab/phase1-python
python server.py
In the web
browser, go to http://localhost:8080 to see the example UI without
reCAPTCHA integrated.
2. Registering with the reCAPTCHA Admin Console
... We recommend if you have
time that you follow through and create your own registration in the
admin console. Before you use reCAPTCHA in development or production
you will need to do this step.
First go to The reCAPTCHA Admin Site.
Choose ‘Invisible reCAPTCHA' as the type of captcha.
Fill in the list of domains you wish to show your captcha. The reCAPTCHA site
key you create will only work on these domains. Save the site key and
secret key for the later stages.
3. Add Invisible reCAPTCHA to the frontend - Python
Let's update the frontend to call Invisible reCAPTCHA with your
favorite text editor.
First let's add a script tag to the html element in
feedback.html.
feedback.html
<html>
<head>
<title>Suggestion page</title>
<script src='https://www.google.com/recaptcha/api.js'></script>
Now update the attributes to the submit button. Add
class='g-recaptcha' data-sitekey="YOUR SITE KEY" and add a
data-callback.
feedback.html
<button class="g-recaptcha"
data-sitekey="6LfeHx4UAAAAAAKUx5rO5nfKMtc9-syDTdFLftnm"
data-callback="onSubmit">Submit</button>
The full file should be:
feedback.html
<!DOCTYPE html>
<!DOCTYPE html>
<html>
<head>
<title>Suggestion page</title>
<script src='https://www.google.com/recaptcha/api.js'></script>
<script>
function onSubmit() {
document.getElementById("demo-form").submit();
}
</script>
<style>
body {
font-family: Helvetica, sans-serif;
}
.status-message {
background-color: #5ff;
margin-bottom: 10px;
text-align: center;
}
textarea {
margin: 10px 0;
resize: none;
}
</style>
</head>
<body>
<h3>Give us feedback on our webpage!</h3>
<div class="status-message">%s</div>
<form id="demo-form" action="/feedback" method="POST">
Your comment <br><textarea name="feedback" cols="50" rows="5"></textarea><br>
<!-- Replace this with your site key -->
<button class="g-recaptcha"
data-sitekey="6LfeHx4UAAAAAAKUx5rO5nfKMtc9-syDTdFLftnm"
data-callback="onSubmit">Submit</button>
</form>
</body>
</html>
3. Validate the Invisible reCAPTCHA on the Server - Python
In "Registering with the reCAPTCHA Admin Console" we created a new
site key. As part of that process a site secret was created. The site
secret is necessary to validate the CAPTCHA solution. For the purposes
of this code lab you can use the default keys provided. These will
only work on localhost.
The validation of the reCAPTCHA token is done by sending a POST
request to https://www.google.com/recaptcha/api/siteverify. The
details are in Verifying the user's response.
To validate the reCAPTCHA token, let's update the server. First we
need to add the site secret and site verify constants.
server.py
SITE_VERIFY_URL = 'https://www.google.com/recaptcha/api/siteverify'
SITE_SECRET = '6LfeHx4UAAAAAFWXGh_xcL0B8vVcXnhn9q_SnQ1b'
RECAPTCHA_RESPONSE_PARAM = 'g-recaptcha-response'
Then we need to update our POST handler to verify the token.
server.py
def do_POST(self):
self.set_headers();
post_body = parse_qs(self.rfile.read(int(self.headers['Content-Length'])))
success = False
if RECAPTCHA_RESPONSE_PARAM in post_body:
token = post_body[RECAPTCHA_RESPONSE_PARAM][0]
resp = urllib.urlopen(
SITE_VERIFY_URL, urllib.urlencode(
{'secret': SITE_SECRET, 'response': token}, True)).read()
if json.loads(resp).get("success"):
success = True
if success:
message = 'Thanks for the feedback!'
else:
message = 'There was an error.'
self.wfile.write(open(curdir + sep + 'feedback.html').read() % message)
The final file should look like this:
server.py
import json
import urllib
from os import curdir, sep
from urlparse import parse_qs
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
SITE_VERIFY_URL = 'https://www.google.com/recaptcha/api/siteverify'
SITE_SECRET = '6LfeHx4UAAAAAFWXGh_xcL0B8vVcXnhn9q_SnQ1b'
RECAPTCHA_RESPONSE_PARAM = 'g-recaptcha-response'
class Handler(BaseHTTPRequestHandler):
def set_headers(self):
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
def do_GET(self):
self.set_headers();
self.wfile.write(open(curdir + sep + 'feedback.html').read() % '')
def do_POST(self):
self.set_headers();
post_body = parse_qs(self.rfile.read(int(self.headers['Content-Length'])))
success = False
if RECAPTCHA_RESPONSE_PARAM in post_body:
token = post_body[RECAPTCHA_RESPONSE_PARAM][0]
resp = urllib.urlopen(
SITE_VERIFY_URL, urllib.urlencode(
{'secret': SITE_SECRET, 'response': token}, True)).read()
if json.loads(resp).get("success"):
success = True
if success:
message = 'Thanks for the feedback!'
else:
message = 'There was an error.'
self.wfile.write(open(curdir + sep + 'feedback.html').read() % message)
if __name__ == '__main__':
httpd = HTTPServer(('', 8080), Handler)
httpd.serve_forever()
You're all done! Now reload the server and give it a try. The
completed version can be found in final-python/server.py. You now have
a basic integration with reCAPTCHA to protect your form. In the
background, we are verifying the user and will sometimes show a
reCAPTCHA challenge to make sure that your website is protected from
abuse. More details and options can be found on our developer site.
Please note: I am an independant software developer with no affiliation with Google.
You should review the Open Web Application Security Project (OWASP) website and their Top 10 vulnerabilities thoroughly. There's a lot of information there. I also recommend abiding by their cheat sheets. This resource is a helpful place to get started. Web security is complex and requires you to be comprehensive in your protection scheme.
Related
I was able to successfully integrate Google Sign-In on my website using the below standard code (with relevant inputs) (see Integrating Google Sign-In). 2 Months later, with no changes to the code, google started throwing the following authorisation error during login:
Error 400: invalid_request Permission denied to generate login hint
for target domain.
<script src="https://apis.google.com/js/platform.js" async defer></script>
<div class="g-signin2" data-onsuccess="onSignIn"></div>
<meta name="google-signin-client_id" content="YOUR_CLIENT_ID.apps.googleusercontent.com">
function onSignIn(googleUser) {
var profile = googleUser.getBasicProfile();
console.log('ID: ' + profile.getId());
console.log('Name: ' + profile.getName());
console.log('Image URL: ' + profile.getImageUrl());
console.log('Email: ' + profile.getEmail());
}
It seems this error is typically caused by incorrectly specifying Authorized JavaScript origins on the Google Cloud Platform, but I have used the actual URL to my website.
I have tried clearing cache
I ran the same code on localhost, using the same google client id (plus additional Authorized JavaScript origins setting) and it worked fine.
The host server is https://www.pythonanywhere.com/ and I have been testing my site on google chrome and safari.
Other users are having exactly the same issues on this domain, so I presume this is a domain related issue?
I'm trying to build a website which has some private pages. And my goal is it that the users have the option to download PDFs from there, but only if they're authenticated. Now my question is, where should I store the PDF files, so that it can only be accessed by the authenticated users and how should I serve/access them. And all that with nextjs, if possible (I use next-auth for authentication).
It's a pretty general question and I don't need specific answer for it but a general way which I should follow or thing that I should look into would be awesome.
I'm kinda new to web development, so sorry if the question is dumb.
Authentication
I think you can combine the power of JavaScript and CSS to achieve this.
After authenticating the user with your desired framework, then navigate inside the component that will display login or sign up, and set up react hook state as this:
const [isAuthenticated, setIsAuthenticated] = useState(false);
Now the token generated by the authentication framework must have been saved. Use if else control to join them as this:
const user = Auth.user({email:abc#gmail.com})
let message
if (user){
setIsAuthenticated(true);
Message = <h3>Welcome {user.username}</h3>
}else {
router.push('/')
}
.
.
.
return(
<div className={isAuthenticated ? 'display': ''}>
<button>download link</button>
</div>
)
Inside the global CSS file,
Add the following lines
.display {
display:none;
}
This should do it.
It will only display the DOM to only authenticated users.
Right now, in gmail appscript we don't have any option to add a password type field.
Gmail Card Service for add-on has a very good ability to show any thing in it. We can integrate with any app which has basic REST api. We need authentication for that which commonly need password type field.
Any work around to show password type field?
As of now, there is no support for password field in Gmail add-on.
But we can build a hack for it. I hope password is needed only in registration forms. So, we can build a registration form using HTML and that can be served through authorization action.
CardService.newAuthorizationAction().setAuthorizationUrl(loginUrl)
Here, host registration HTML in a web server and pass this URL as "loginUrl" in the above snippet. We have to supply AuthorizationAction for the signup/register button. So, when the user clicks on this button, a new popup page is launched, the user will give the username, password, etc... onsubmit, we can encode all the form data and pass it to the parent Gmail add-on by redirecting it to a script redirection URL which you can generate an add-on. Once the redirection to the script URL comes, there will be a callback in our add-on code from there you can get the form fields which were encoded from registration HTML page.
function generateNewStateToken(callbackName, payload) {
return ScriptApp.newStateToken()
.withMethod(callbackName)
.withArgument("payload", JSON.stringify(payload))
.withTimeout(3600)
.createToken();
}
function getRedirectURI() {
return "https://script.google.com/macros/d/" + ScriptApp.getScriptId() + "/usercallback";
}
var state = generateNewStateToken("registerCallback", {"signup": true});
var reg_url = <reg_url> + "?redirect_uri=" + getRedirectURI() + "&state=" + state;
function registerCallback(cbResp) {
// to access payload which passed in state token: cbResp.parameter.payload;
// in the html serialize all the form fields or data which you want to pass to plugin as query params like: <redirect_uri>?form_data=<encoded_data>&state=<state>
//Note: here the registration HTML page should parse the URL to get the state & redirect_uri from URL.
// to access form_data: cbResp.parameter.form_data
}
I hope this will help you. This is how we are doing the signup/signin flow now.
Looks like you are authorizing a non google service . Please refer to Authorizing custom google services .
I am facing roadblock on a owasp zap form based authentication. I setup zap property as per guidance. When i run active scan then "when to attempt login it give FORBIDDEN error. CSRF token not available.
Owasp ZAP not performing authentication during active scan using "Form-Based-Authentication" ON python project.
[
My target url is:
http://example.com:84/admin/login/?next=/admin/
Post data ;
csrfmiddlewaretoken=IjYwHHavnCYgcWYMy2oL3L9Z0ldUH95s&username={%username%}&password={%password%}&next=%2Fadmin%2F
here is the html response i got:
<div id="summary">
<h1>Forbidden <span>(403)</span></h1>
<p>CSRF verification failed. Request aborted.</p>
</div>
<div id="info">
<h2>Help</h2>
<p>Reason given for failure:</p>
<pre>
CSRF token missing or incorrect.
</pre>
<p>In general, this can occur when there is a genuine Cross Site Request Forgery, or when
<a
href="https://docs.djangoproject.com/en/1.8/ref/csrf/">Django's
CSRF mechanism</a> has not been used correctly. For POST forms, you need to
ensure:</p>
<ul>
<li>Your browser is accepting cookies.</li>
<li>The view function passes a <code>request</code> to the template's <code>render</code>
method.</li>
<li>In the template, there is a <code>{% csrf_token
%}</code> template tag inside each POST form that
targets an internal URL.</li>
<li>If you are not using <code>CsrfViewMiddleware</code>, then you must use
<code>csrf_protect</code> on any views that use the <code>csrf_token</code>
template tag, as well as those that accept the POST data.</li>
</ul>
<p>You're seeing the help section of this page because you have <code>DEBUG =
True</code> in your Django settings file. Change that to <code>False</code>,
and only the initial error message will be displayed. </p>
<p>You can customize this page using the CSRF_FAILURE_VIEW setting.</p>
</div>
Unfortunatley ZAP doesnt currently support the automatic regeneration of CSRF tokens when authenticating.
A way around this is to record a Zest authentication script - make sure that you start by requesting the page token that generates that token.
Recording Zest scripts is covered in this FAQ (which is otherwise unrelated): https://github.com/zaproxy/zaproxy/wiki/FAQreportFN
Feel free to hassle us about supporting ACSR toeksn when authenticating on https://groups.google.com/group/zaproxy-users :)
I have a Home screen with login button. When ever user clicks the Login button login view must open in dialog(popUp window). Here the twist is Home Screen and Login Screen are two different apps in two sub domains.
So I have to open Login View of auth.sample.com in dialog(PopUp window) from my.sample.com app.
Sorry for the bad English. Please see the code below:
<p>
<button type="submit" name="PopupLoginbutton" id = "PopupLoginbutton" value="Popbutton">Login</button>
</p>
<div id="dialog" title="Login" style="overflow: hidden;">
<script type="text/javascript">
$(function () {
$('#dialog').dialog({
autoOpen: false,
width: 400,
resizable: false,
title: 'hi there',
modal: true,
open: function (event, ui) {
$(this).load("http://auth.sample.com/");
},
buttons: {
"Close": function () {
$(this).dialog("close");
}
}
});
$('#PopupLoginbutton').click(function () {
$('#dialog').dialog('open');
});
});
</script>
Views can only be accessed if they are in the current MVC3 project. If all you want to do is show the login screen, you should just show the popup using the full URL (http://my.sample.com/login).
If you want to pass information to the login app, then you would have to use a form and POST/GET to the full URL.
If I understand your position correctly, you want to provide access to content or an area of your site, but only if users are logged in. Are you looking to provide log in capabilities to your new site through the existing auth of your subdomain?
Unfortunately this won't work as you describe, if you're using common methods for authentication and authorization. Logging into a different sub domain isn't going to get you where you're trying to go. I can't log into my bank and then check my email.
You can POST wherever you like or even use JS to load content from other sites/domains (if the end-users permissions/config allow), but your token in the browser to represent a logged in user won't carry over from one domain to the next.
There are several approaches, here are a couple of common ones:
Create a custom MembershipProvider and RoleProvider. These would allow you to use the server-side logic you already have to your access your authentication service.
Configure your new site to use the same membership provider (and database) that the subdomain is using.
Build a web service and tokenization strategy to expose identity from your existing subdomain, and incorporate that into your site.
Number 2 is the easiest, if you own the subdomain. Number 1 isn't too hard if you can wrap the calls to the membership service. If your subdomain is third party, however, and it locks up the user database then you won't have much luck here, unless they provide a means to wrap their login services.
Hope this helps some.
Cheers.