Authenticating an internal API with FlaskContest assist web appAuthentication for a Flask APIIs this implementation for token based authentication in flask-peewee secure and efficient?Custom Google App Engine Python user managmentJinja template with FlaskRead stdin like a dictatorFunction to lock a file using memcache, Version 1Reduce amount of calls to database for authenticationCloudFlare Dynamic DNS Update Script in Python 3Dynamically configurable ZMQ filter with Flask API
Student asking for papers
Farming on the moon
Is there really no use for MD5 anymore?
Authenticating an internal API with Flask
How to remove these lines in Altium Design
Is the claim "Employers won't employ people with no 'social media presence'" realistic?
diskutil list shows 20 disk partitions, I only know 3, what are the rest?
Ergodic without atoms implies completely conservative?
Critique of timeline aesthetic
Is Diceware more secure than a long passphrase?
How much cash can I safely carry into the USA and avoid civil forfeiture?
Could the terminal length of components like resistors be reduced?
What happened to Captain America in Endgame?
How does Captain America channel this power?
What is the optimal strategy for the Dictionary Game?
Why must Chinese maps be obfuscated?
How could Tony Stark make this in Endgame?
Latex syntax: parenthesis for makebox(0,0)
Was there a Viking Exchange as well as a Columbian one?
Retract an already submitted recommendation letter (written for an undergrad student)
If a planet has 3 moons, is it possible to have triple Full/New Moons at once?
How exactly does Hawking radiation decrease the mass of black holes?
Big O /Right or wrong?
How do I deal with a coworker that keeps asking to make small superficial changes to a report, and it is seriously triggering my anxiety?
Authenticating an internal API with Flask
Contest assist web appAuthentication for a Flask APIIs this implementation for token based authentication in flask-peewee secure and efficient?Custom Google App Engine Python user managmentJinja template with FlaskRead stdin like a dictatorFunction to lock a file using memcache, Version 1Reduce amount of calls to database for authenticationCloudFlare Dynamic DNS Update Script in Python 3Dynamically configurable ZMQ filter with Flask API
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty,.everyoneloves__bot-mid-leaderboard:empty margin-bottom:0;
$begingroup$
I'm developing an internal API using Flask, although due to limitations with our platform the endpoints will be accessible over the public internet. It will only have a very small number of users and it's not likely that this will increase much in the future. They will query the service programatically from a backend.
As only a small number of users will be consuming the API I think it makes sense to have a single key that can be used to access it. This will keep the authentication process simple and as the API doesn't provide access to anything really sensitive I don't believe we need to go too extreme on security. I've written the following:
app_config.py
import yaml
config = yaml.safe_load(open('config.yml'))
auth.py
from functools import wraps
from flask import request
from app_config import config
def valid_auth(func):
@wraps(func)
def func_wrapper(*args, **kwargs):
if 'x-api-key' not in request.headers:
return("Credentials not present in request", 401)
elif request.headers['x-api-key'] != config['api_key']:
return ("Credentials not valid", 401)
else:
return func(*args, **kwargs)
return func_wrapper
main.py
import api_module as api
from flask import Flask, request
from auth import valid_auth
app = Flask(__name__)
@app.route('/route1')
@valid_auth
def api_function():
#do api stuff here
Essentially the process is:
- API key is stored in config.yml on API server
- (SSL) Request from backend includes the key in a header called x-api-key
- A wrapper function checks that the key sent in this header matches the key in the configuration before executing any API functions. If not, the user gets an error.
Does this seem like a reasonable approach to authentication for an internal API that will be used by a small number of users and that doesn't provide access to any sensitive information? Any general suggestions for how this process might be improved?
python python-3.x authentication flask
New contributor
$endgroup$
add a comment |
$begingroup$
I'm developing an internal API using Flask, although due to limitations with our platform the endpoints will be accessible over the public internet. It will only have a very small number of users and it's not likely that this will increase much in the future. They will query the service programatically from a backend.
As only a small number of users will be consuming the API I think it makes sense to have a single key that can be used to access it. This will keep the authentication process simple and as the API doesn't provide access to anything really sensitive I don't believe we need to go too extreme on security. I've written the following:
app_config.py
import yaml
config = yaml.safe_load(open('config.yml'))
auth.py
from functools import wraps
from flask import request
from app_config import config
def valid_auth(func):
@wraps(func)
def func_wrapper(*args, **kwargs):
if 'x-api-key' not in request.headers:
return("Credentials not present in request", 401)
elif request.headers['x-api-key'] != config['api_key']:
return ("Credentials not valid", 401)
else:
return func(*args, **kwargs)
return func_wrapper
main.py
import api_module as api
from flask import Flask, request
from auth import valid_auth
app = Flask(__name__)
@app.route('/route1')
@valid_auth
def api_function():
#do api stuff here
Essentially the process is:
- API key is stored in config.yml on API server
- (SSL) Request from backend includes the key in a header called x-api-key
- A wrapper function checks that the key sent in this header matches the key in the configuration before executing any API functions. If not, the user gets an error.
Does this seem like a reasonable approach to authentication for an internal API that will be used by a small number of users and that doesn't provide access to any sensitive information? Any general suggestions for how this process might be improved?
python python-3.x authentication flask
New contributor
$endgroup$
add a comment |
$begingroup$
I'm developing an internal API using Flask, although due to limitations with our platform the endpoints will be accessible over the public internet. It will only have a very small number of users and it's not likely that this will increase much in the future. They will query the service programatically from a backend.
As only a small number of users will be consuming the API I think it makes sense to have a single key that can be used to access it. This will keep the authentication process simple and as the API doesn't provide access to anything really sensitive I don't believe we need to go too extreme on security. I've written the following:
app_config.py
import yaml
config = yaml.safe_load(open('config.yml'))
auth.py
from functools import wraps
from flask import request
from app_config import config
def valid_auth(func):
@wraps(func)
def func_wrapper(*args, **kwargs):
if 'x-api-key' not in request.headers:
return("Credentials not present in request", 401)
elif request.headers['x-api-key'] != config['api_key']:
return ("Credentials not valid", 401)
else:
return func(*args, **kwargs)
return func_wrapper
main.py
import api_module as api
from flask import Flask, request
from auth import valid_auth
app = Flask(__name__)
@app.route('/route1')
@valid_auth
def api_function():
#do api stuff here
Essentially the process is:
- API key is stored in config.yml on API server
- (SSL) Request from backend includes the key in a header called x-api-key
- A wrapper function checks that the key sent in this header matches the key in the configuration before executing any API functions. If not, the user gets an error.
Does this seem like a reasonable approach to authentication for an internal API that will be used by a small number of users and that doesn't provide access to any sensitive information? Any general suggestions for how this process might be improved?
python python-3.x authentication flask
New contributor
$endgroup$
I'm developing an internal API using Flask, although due to limitations with our platform the endpoints will be accessible over the public internet. It will only have a very small number of users and it's not likely that this will increase much in the future. They will query the service programatically from a backend.
As only a small number of users will be consuming the API I think it makes sense to have a single key that can be used to access it. This will keep the authentication process simple and as the API doesn't provide access to anything really sensitive I don't believe we need to go too extreme on security. I've written the following:
app_config.py
import yaml
config = yaml.safe_load(open('config.yml'))
auth.py
from functools import wraps
from flask import request
from app_config import config
def valid_auth(func):
@wraps(func)
def func_wrapper(*args, **kwargs):
if 'x-api-key' not in request.headers:
return("Credentials not present in request", 401)
elif request.headers['x-api-key'] != config['api_key']:
return ("Credentials not valid", 401)
else:
return func(*args, **kwargs)
return func_wrapper
main.py
import api_module as api
from flask import Flask, request
from auth import valid_auth
app = Flask(__name__)
@app.route('/route1')
@valid_auth
def api_function():
#do api stuff here
Essentially the process is:
- API key is stored in config.yml on API server
- (SSL) Request from backend includes the key in a header called x-api-key
- A wrapper function checks that the key sent in this header matches the key in the configuration before executing any API functions. If not, the user gets an error.
Does this seem like a reasonable approach to authentication for an internal API that will be used by a small number of users and that doesn't provide access to any sensitive information? Any general suggestions for how this process might be improved?
python python-3.x authentication flask
python python-3.x authentication flask
New contributor
New contributor
New contributor
asked 4 hours ago
Duck Hunt DuoDuck Hunt Duo
182
182
New contributor
New contributor
add a comment |
add a comment |
1 Answer
1
active
oldest
votes
$begingroup$
Just because you don't have many people using this API now, this does not mean that this will always be the case.
In addition, you might want to discriminate between the users of the API for various reasons:
- To know how many there actually are
- To know which user/application is generating all those requests suddenly
- To rate-throttle some of them if needed
- To disallow access for someone who abused the API or just left the company or an obsolete application
I would at least set this up in a very barebone way to have different API keys. For now you can just hardcode them in a dictionary in the config file, but this allows you to easily extract them to a database at some future point (which may never come). You can manually add users when needed (if it is only a few), or write a page that adds a user (if the manual task becomes too much).
In your config just have something like this:
config.yml
api_keys:
08zEk8IC0le3I0kPwSF1g4XU9R5WgbpUq2vZkZ0pkQU: User 1
ZtRE7FXwZdtCLMfFHWPTom7_d-4XFbXEkHR5bIdG2TM: User 2
Wg1vaDs8uqFbYtNDsJ8H3gKjl_oI0T_O6Jg8qNLWJcU: App 1
...
Your code needs to be only minimally changed:
auth.py
def valid_auth(func):
@wraps(func)
def func_wrapper(*args, **kwargs):
if 'x-api-key' not in request.headers:
return "Credentials not present in request", 401
elif request.headers['x-api-key'] not in config['api_keys']:
return "Credentials not valid", 401
else:
return func(*args, **kwargs)
return func_wrapper
Note that ()
around tuples are not needed in return
since it is an expression and not a function.
Returning a tuple of message, status code to raise in one case and the result of the function in another case can also be a potential source of bugs. If the using code expects the return value to be a single value, checking for there being two and those two being a string and an int is not very fool-proof. Even worse if the function actually also returns a string and int tuple.
Instead, raise exceptions, which you can then deal with in api_function
. Use custom classes inheriting from Exception
:
class NoCredentials(Exception):
status_code = 401
class WrongCredentials(Exception):
status_code = 401
...
if 'x-api-key' not in request.headers:
raise NoCredentials("Credentials not present in request")
This page in the official documentation explains in more detail how to use custom exceptions correctly in flask.
$endgroup$
1
$begingroup$
Thanks a lot for the advice. The info about raising exceptions is particularly useful. I think I will implement separate keys too for the sake of following a good practice, although in this case I feel pretty safe saying that the number of users isn't going to grow significantly in the future (it will be in the single digits). Thanks!
$endgroup$
– Duck Hunt Duo
1 hour ago
add a comment |
Your Answer
StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");
StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "196"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);
else
createEditor();
);
function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader:
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
,
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);
);
Duck Hunt Duo is a new contributor. Be nice, and check out our Code of Conduct.
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f219163%2fauthenticating-an-internal-api-with-flask%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
$begingroup$
Just because you don't have many people using this API now, this does not mean that this will always be the case.
In addition, you might want to discriminate between the users of the API for various reasons:
- To know how many there actually are
- To know which user/application is generating all those requests suddenly
- To rate-throttle some of them if needed
- To disallow access for someone who abused the API or just left the company or an obsolete application
I would at least set this up in a very barebone way to have different API keys. For now you can just hardcode them in a dictionary in the config file, but this allows you to easily extract them to a database at some future point (which may never come). You can manually add users when needed (if it is only a few), or write a page that adds a user (if the manual task becomes too much).
In your config just have something like this:
config.yml
api_keys:
08zEk8IC0le3I0kPwSF1g4XU9R5WgbpUq2vZkZ0pkQU: User 1
ZtRE7FXwZdtCLMfFHWPTom7_d-4XFbXEkHR5bIdG2TM: User 2
Wg1vaDs8uqFbYtNDsJ8H3gKjl_oI0T_O6Jg8qNLWJcU: App 1
...
Your code needs to be only minimally changed:
auth.py
def valid_auth(func):
@wraps(func)
def func_wrapper(*args, **kwargs):
if 'x-api-key' not in request.headers:
return "Credentials not present in request", 401
elif request.headers['x-api-key'] not in config['api_keys']:
return "Credentials not valid", 401
else:
return func(*args, **kwargs)
return func_wrapper
Note that ()
around tuples are not needed in return
since it is an expression and not a function.
Returning a tuple of message, status code to raise in one case and the result of the function in another case can also be a potential source of bugs. If the using code expects the return value to be a single value, checking for there being two and those two being a string and an int is not very fool-proof. Even worse if the function actually also returns a string and int tuple.
Instead, raise exceptions, which you can then deal with in api_function
. Use custom classes inheriting from Exception
:
class NoCredentials(Exception):
status_code = 401
class WrongCredentials(Exception):
status_code = 401
...
if 'x-api-key' not in request.headers:
raise NoCredentials("Credentials not present in request")
This page in the official documentation explains in more detail how to use custom exceptions correctly in flask.
$endgroup$
1
$begingroup$
Thanks a lot for the advice. The info about raising exceptions is particularly useful. I think I will implement separate keys too for the sake of following a good practice, although in this case I feel pretty safe saying that the number of users isn't going to grow significantly in the future (it will be in the single digits). Thanks!
$endgroup$
– Duck Hunt Duo
1 hour ago
add a comment |
$begingroup$
Just because you don't have many people using this API now, this does not mean that this will always be the case.
In addition, you might want to discriminate between the users of the API for various reasons:
- To know how many there actually are
- To know which user/application is generating all those requests suddenly
- To rate-throttle some of them if needed
- To disallow access for someone who abused the API or just left the company or an obsolete application
I would at least set this up in a very barebone way to have different API keys. For now you can just hardcode them in a dictionary in the config file, but this allows you to easily extract them to a database at some future point (which may never come). You can manually add users when needed (if it is only a few), or write a page that adds a user (if the manual task becomes too much).
In your config just have something like this:
config.yml
api_keys:
08zEk8IC0le3I0kPwSF1g4XU9R5WgbpUq2vZkZ0pkQU: User 1
ZtRE7FXwZdtCLMfFHWPTom7_d-4XFbXEkHR5bIdG2TM: User 2
Wg1vaDs8uqFbYtNDsJ8H3gKjl_oI0T_O6Jg8qNLWJcU: App 1
...
Your code needs to be only minimally changed:
auth.py
def valid_auth(func):
@wraps(func)
def func_wrapper(*args, **kwargs):
if 'x-api-key' not in request.headers:
return "Credentials not present in request", 401
elif request.headers['x-api-key'] not in config['api_keys']:
return "Credentials not valid", 401
else:
return func(*args, **kwargs)
return func_wrapper
Note that ()
around tuples are not needed in return
since it is an expression and not a function.
Returning a tuple of message, status code to raise in one case and the result of the function in another case can also be a potential source of bugs. If the using code expects the return value to be a single value, checking for there being two and those two being a string and an int is not very fool-proof. Even worse if the function actually also returns a string and int tuple.
Instead, raise exceptions, which you can then deal with in api_function
. Use custom classes inheriting from Exception
:
class NoCredentials(Exception):
status_code = 401
class WrongCredentials(Exception):
status_code = 401
...
if 'x-api-key' not in request.headers:
raise NoCredentials("Credentials not present in request")
This page in the official documentation explains in more detail how to use custom exceptions correctly in flask.
$endgroup$
1
$begingroup$
Thanks a lot for the advice. The info about raising exceptions is particularly useful. I think I will implement separate keys too for the sake of following a good practice, although in this case I feel pretty safe saying that the number of users isn't going to grow significantly in the future (it will be in the single digits). Thanks!
$endgroup$
– Duck Hunt Duo
1 hour ago
add a comment |
$begingroup$
Just because you don't have many people using this API now, this does not mean that this will always be the case.
In addition, you might want to discriminate between the users of the API for various reasons:
- To know how many there actually are
- To know which user/application is generating all those requests suddenly
- To rate-throttle some of them if needed
- To disallow access for someone who abused the API or just left the company or an obsolete application
I would at least set this up in a very barebone way to have different API keys. For now you can just hardcode them in a dictionary in the config file, but this allows you to easily extract them to a database at some future point (which may never come). You can manually add users when needed (if it is only a few), or write a page that adds a user (if the manual task becomes too much).
In your config just have something like this:
config.yml
api_keys:
08zEk8IC0le3I0kPwSF1g4XU9R5WgbpUq2vZkZ0pkQU: User 1
ZtRE7FXwZdtCLMfFHWPTom7_d-4XFbXEkHR5bIdG2TM: User 2
Wg1vaDs8uqFbYtNDsJ8H3gKjl_oI0T_O6Jg8qNLWJcU: App 1
...
Your code needs to be only minimally changed:
auth.py
def valid_auth(func):
@wraps(func)
def func_wrapper(*args, **kwargs):
if 'x-api-key' not in request.headers:
return "Credentials not present in request", 401
elif request.headers['x-api-key'] not in config['api_keys']:
return "Credentials not valid", 401
else:
return func(*args, **kwargs)
return func_wrapper
Note that ()
around tuples are not needed in return
since it is an expression and not a function.
Returning a tuple of message, status code to raise in one case and the result of the function in another case can also be a potential source of bugs. If the using code expects the return value to be a single value, checking for there being two and those two being a string and an int is not very fool-proof. Even worse if the function actually also returns a string and int tuple.
Instead, raise exceptions, which you can then deal with in api_function
. Use custom classes inheriting from Exception
:
class NoCredentials(Exception):
status_code = 401
class WrongCredentials(Exception):
status_code = 401
...
if 'x-api-key' not in request.headers:
raise NoCredentials("Credentials not present in request")
This page in the official documentation explains in more detail how to use custom exceptions correctly in flask.
$endgroup$
Just because you don't have many people using this API now, this does not mean that this will always be the case.
In addition, you might want to discriminate between the users of the API for various reasons:
- To know how many there actually are
- To know which user/application is generating all those requests suddenly
- To rate-throttle some of them if needed
- To disallow access for someone who abused the API or just left the company or an obsolete application
I would at least set this up in a very barebone way to have different API keys. For now you can just hardcode them in a dictionary in the config file, but this allows you to easily extract them to a database at some future point (which may never come). You can manually add users when needed (if it is only a few), or write a page that adds a user (if the manual task becomes too much).
In your config just have something like this:
config.yml
api_keys:
08zEk8IC0le3I0kPwSF1g4XU9R5WgbpUq2vZkZ0pkQU: User 1
ZtRE7FXwZdtCLMfFHWPTom7_d-4XFbXEkHR5bIdG2TM: User 2
Wg1vaDs8uqFbYtNDsJ8H3gKjl_oI0T_O6Jg8qNLWJcU: App 1
...
Your code needs to be only minimally changed:
auth.py
def valid_auth(func):
@wraps(func)
def func_wrapper(*args, **kwargs):
if 'x-api-key' not in request.headers:
return "Credentials not present in request", 401
elif request.headers['x-api-key'] not in config['api_keys']:
return "Credentials not valid", 401
else:
return func(*args, **kwargs)
return func_wrapper
Note that ()
around tuples are not needed in return
since it is an expression and not a function.
Returning a tuple of message, status code to raise in one case and the result of the function in another case can also be a potential source of bugs. If the using code expects the return value to be a single value, checking for there being two and those two being a string and an int is not very fool-proof. Even worse if the function actually also returns a string and int tuple.
Instead, raise exceptions, which you can then deal with in api_function
. Use custom classes inheriting from Exception
:
class NoCredentials(Exception):
status_code = 401
class WrongCredentials(Exception):
status_code = 401
...
if 'x-api-key' not in request.headers:
raise NoCredentials("Credentials not present in request")
This page in the official documentation explains in more detail how to use custom exceptions correctly in flask.
edited 59 mins ago
answered 3 hours ago
GraipherGraipher
27.8k54499
27.8k54499
1
$begingroup$
Thanks a lot for the advice. The info about raising exceptions is particularly useful. I think I will implement separate keys too for the sake of following a good practice, although in this case I feel pretty safe saying that the number of users isn't going to grow significantly in the future (it will be in the single digits). Thanks!
$endgroup$
– Duck Hunt Duo
1 hour ago
add a comment |
1
$begingroup$
Thanks a lot for the advice. The info about raising exceptions is particularly useful. I think I will implement separate keys too for the sake of following a good practice, although in this case I feel pretty safe saying that the number of users isn't going to grow significantly in the future (it will be in the single digits). Thanks!
$endgroup$
– Duck Hunt Duo
1 hour ago
1
1
$begingroup$
Thanks a lot for the advice. The info about raising exceptions is particularly useful. I think I will implement separate keys too for the sake of following a good practice, although in this case I feel pretty safe saying that the number of users isn't going to grow significantly in the future (it will be in the single digits). Thanks!
$endgroup$
– Duck Hunt Duo
1 hour ago
$begingroup$
Thanks a lot for the advice. The info about raising exceptions is particularly useful. I think I will implement separate keys too for the sake of following a good practice, although in this case I feel pretty safe saying that the number of users isn't going to grow significantly in the future (it will be in the single digits). Thanks!
$endgroup$
– Duck Hunt Duo
1 hour ago
add a comment |
Duck Hunt Duo is a new contributor. Be nice, and check out our Code of Conduct.
Duck Hunt Duo is a new contributor. Be nice, and check out our Code of Conduct.
Duck Hunt Duo is a new contributor. Be nice, and check out our Code of Conduct.
Duck Hunt Duo is a new contributor. Be nice, and check out our Code of Conduct.
Thanks for contributing an answer to Code Review Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
Use MathJax to format equations. MathJax reference.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f219163%2fauthenticating-an-internal-api-with-flask%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown