So I'm working with AppEngine (Python) and what I want to do is to provide OpenID Login setting a Default provider so the user can Log-In without problems using that provider. The thing is, I want to prompt the user a Password right after they login in order to show static content (HTML Pages); If the user doesn't enter the correct password then I want to redirect them to another page. The protection has to be server side please :) Any Ideas??
P.S. I'm seeking for a solution similar to ".htaccess/htpasswd" but for app engine.
AFAIK, GAE does not support such setup (static password after OpenID login).
The only way I see to make this work would be to serve static content via your handler:
Client makes a request for static content
Your handler is registered to handle this URL
Handler checks is user is authenticated. If not, requests a password.
When authenticated, handler reads static file and sends it back to user.
Try this out, you can mimic the .htaccess style password with Google App Engine:
def basicAuth(func):
def callf(webappRequest, *args, **kwargs):
# Parse the header to extract a user/password combo.
# We're expecting something like "Basic XZxgZRTpbjpvcGVuIHYlc4FkZQ=="
auth_header = webappRequest.request.headers.get('Authorization')
if auth_header == None:
webappRequest.response.set_status(401, message="Authorization Required")
webappRequest.response.headers['WWW-Authenticate'] = 'Basic realm="Kalydo School"'
else:
# Isolate the encoded user/passwd and decode it
auth_parts = auth_header.split(' ')
user_pass_parts = base64.b64decode(auth_parts[1]).split(':')
user_arg = user_pass_parts[0]
pass_arg = user_pass_parts[1]
if user_arg != "admin" or pass_arg != "foobar":
webappRequest.response.set_status(401, message="Authorization Required")
webappRequest.response.headers['WWW-Authenticate'] = 'Basic realm="Secure Area"'
# Rendering a 401 Error page is a good way to go...
self.response.out.write(template.render('templates/error/401.html', {}))
else:
return func(webappRequest, *args, **kwargs)
return callf
class AuthTest(webapp.RequestHandler):
#basicAuth
def get(self):
....
How-To: Dynamic WWW-Authentication (.htaccess style) on Google App Engine
Related
I have two separate Flask applications, one is an API with the domain "admin.company.com" and the second one is a dashboard under the domain "dashboard.company.com".
My main goal is to secure the api and enable authentication.
I set up authentication on the api and when I'm testing it via the swagger-ui it works good with no issues. I manage to authenticate myself and submit requests. On top of that, in the token_required() below I coded a section that expects to receive JWT and decode it:
def token_required(f):
#wraps(f)
def decorator(*args, **kwargs):
token = None
if 'jwt-token' in request.headers:
token = request.headers['jwt-token']
if not token:
return jsonify({'message': 'a valid token is missing'})
try:
current_user = False
# for authentication via the swagger-ui
if token == 'my_awesome_password':
current_user = 'admin'
else:
data = jwt.decode(token, app.secret_key)
current_user = 'admin' if data['public_id'] == 'admin' else False
if not current_user:
return jsonify({'message': 'token is invalid'})
except:
return jsonify({'message': 'token is invalid'})
return f(*args, **kwargs)
return decorator
The problem is with the dashboard application:
On the dashboard app, I configured the /login route to generate JWT (which needs to be sent to the api app in every HTTP request I made), then do a set_cookie() with httpOnly=True flag (for security purposes), but then how can the JavaScript access it when it has to make XHR requests? Having the httpOnly flag on makes it unreachable.
If using httpOnly=True is not the ideal way in this case, how can I tackle it and make sure the JWT will always be sent to the api app in the request headers?
I'm using PyJWT package.
Thanks for any help.
If you use JWTs for XHR requests, then you don't necessarily need to use cookies – the browser/consumer could store the token and attach it to every request. If you do want to use cookies (this is also a viable option) then you don't need to worry about attaching the JWT as the browser will do that automatically for the domains you specified in the set_cookie command.
I'm stuck in a loop, I have a web app, the login and signup was made using Flask on the Backend and Jinja for templating, no JS was used for these two routes, I used WTForms for form validation and Flask Login to handle login .
Now, when user login he is redirected to a dashboard, the dashboard is a single page application that use React, I send HTTP request to my API which is built in Flask (same app, but different blueprints).
The problem is that these APIs routes are avalable only for authenticated users, so I used a token system to authenticate API calls from the client to the server .
To do so, I created a request loader for my login manager, that using a token parameter in the query would decode it , get the email and password and return the respective User, otherwise if it fail it returns None
#login_manager.request_loader
def load_user(request):
if "token" in request.args:
decoded_token = base64.standard_b64decode(request.args["token"])
email = decoded_token.split(b':',1)[0]
password = decoded_token.split(b':',1)[1]
possible_user = User.select().where(User.email == email)[0]
if possible_user.password.encode() == password:
return possible_user
else:
return None
else:
return None
return None
This token is sent to the user when he login and get redirected to the dashboard :
User login successfULY
Get redirected to dashboard
Dashboard make an ajax call to "/user-data" , which is protected, this route should use current_user to encode the token and send it back to dashboard single page app to use it in further API calls .
The problem:
When I request the "/user-data" through AJAX from dashboard, current_user is empty thus, the call return a 401 unauthorized request even though the user did login, and in the login route, when I print current_user I get the current user logged in as expected . So my question is how can I keep some way to exhcange credentials between login and "user-data" route ? I tried storing the data in a session in the login route then re-use it in the '/user-data' but the session becomes empty whenever 'user-data' is called .
Here's 'user-data' route :
#auth_bp.route("/user-data")
#login_required
def user_data():
# Return Base 64 encode username:password to use in API calls!
print("Current user")
print(current_user)
print(session)
code = base64.standard_b64encode(current_user.email.encode() + b':' + current_user.password.encode())
print(base64.standard_b64decode(code))
return code
I have a simple bokeh server application and I want to expose it on a Linux-based Azure node. The server is there up and running.
My question is: how to protect the content by username and password? I do not need necessarily authentication of users.
My ideas so far (not tried, may not work)
To create an extra bokeh server page with a text field.
On the callback for a button, to add the test if the password fits. If it does, to redirect to the original server page. Otherwise, inform the user about wrong credentials.
You can try to disable generation of session id's by bokeh server and generate them by external application only after user authentication:
(Based on this part of bokeh documentation)
Generate secret key with bokeh secret command:
$ bokeh secret
oIWDL7DVYCaBJG9eYQ2Wvf2f2uhOAIM8xNS8Kds3eizV
Set BOKEH_SECRET_KEY environment variable to generated value;
$ export BOKEH_SECRET_KEY=oIWDL7DVYCaBJG9eYQ2Wvf2f2uhOAIM8xNS8Kds3eizV
Set another environment variable:
$ export BOKEH_SIGN_SESSIONS=True
Run bokeh server with --session-ids external-signed argument:
$ bokeh serve myApp --session-ids external-signed
In this mode user should provide valid (signed) session id to access bokeh server.
Run simple external process to ask users for login and password and generate id's for them.
Here is the example based on snippet from Flask documentation:
from functools import wraps
from flask import request, Response, redirect, Flask
from bokeh.util import session_id
app = Flask(__name__)
def check_auth(username, password):
return username == 'valid_user' and password == 'valid_password'
def authenticate():
"""Sends a 401 response that enables basic auth"""
return Response(
'Could not verify your access level for that URL.\n'
'You have to login with proper credentials', 401,
{'WWW-Authenticate': 'Basic realm="Login Required"'})
def requires_auth(f):
#wraps(f)
def decorated(*args, **kwargs):
auth = request.authorization
if not auth or not check_auth(auth.username, auth.password):
return authenticate()
return f(*args, **kwargs)
return decorated
#app.route('/')
#requires_auth
def redirect_to_bokeh():
s_id = session_id.generate_session_id()
return redirect("http://<bokeh-server-addr>:<port>/?bokeh-session-id={}".format(s_id), code=302)
if __name__ == "__main__":
app.run()
Now to access bokeh server user should go to Flask application and specify login and password.
I have my Flask app hosted in IIS in our intranet. In Flask, I'm able to get the www-authenticate header, but I need to determine the windows username. I did have Basic Authentication enabled and was able to parse out the username via that method, but I want this to be transparent to the user. In IE I have the option set to auto login to intranet sites so they're not prompted for a username and password.
I am able to get a string that can either begin with NTLM or Negotiate (depending on the setting in IIS) and a long auth string. What is a reliable way I can decode this in python/Flask?
Got it.
class RemoteUserMiddleware(object):
def __init__(self, app):
self.app = app
def __call__(self, environ, start_response):
user = environ.pop('HTTP_X_PROXY_REMOTE_USER', None)
environ['REMOTE_USER'] = user
return self.app(environ, start_response)
app.wsgi_app = RemoteUserMiddleware(app.wsgi_app)
Then in the view by doing this:
username = str(request.environ.get('LOGON_USER'))
Maybe a stupid question here:
Is Requests(A python HTTP lib) support Django 1.4 ?
I use Requests follow the Official Quick Start like below:
requests.get('http://127.0.0.1:8000/getAllTracks', auth=('myUser', 'myPass'))
but i never get authentication right.(Of course i've checked the url, username, password again and again.)
The above url 'http://127.0.0.1:8000/getAllTracks' matches an url pattern of the url.py of a Django project, and that url pattern's callback is the 'getAllTracks' view of a Django app.
If i comment out the authentication code of the 'getAllTracks' view, then the above code works OK, but if i add those authentication code back for the view, then the above code never get authenticated right.
The authentication code of the view is actually very simple, exactly like below (The second line):
def getAllTracks(request):
if request.user.is_authenticated():
tracks = Tracks.objects.all()
if tracks:
# Do sth. here
Which means if i delete the above second line(with some indents adjustments of course), then the requests.get() operation do the right thing for me, but if not(keep the second line), then it never get it right.
Any help would be appreciated.
In Django authentication works in following way:
There is a SessionMiddleware and AuthenticationMiddleware. The process_request() of both these classes is called before any view is called.
SessionMiddleware uses cookies at a lower level. It checks for a cookie named sessionid and try to associate this cookie with a user.
AuthenticationMiddleware checks if this cookie is associated with an user then sets request.user as that corresponding user. If the cookie sessionid is not found or can't be associated with any user, then request.user is set to an instance of AnonymousUser().
Since Http is a stateless protocol, django maintains session for a particular user using these two middlewares and using cookies at a lower level.
Coming to the code, so that requests can work with django.
You must first call the view where you authenticate and login the user. The response from this view will contain sessionid in cookies.
You should use this cookie and send it in the next request so that django can authenticate this particular user and so that your request.user.is_authenticated() passes.
from django.contrib.auth import authenticate, login
def login_user(request):
user = authenticate(username=request.POST.get('username'), password=request.POST.get('password'))
if user:
login(request, user)
return HttpResponse("Logged In")
return HttpResponse("Not Logged In")
def getAllTracks(request):
if request.user.is_authenticated():
return HttpResponse("Authenticated user")
return HttpResponse("Non Authenticated user")
Making the requests:
import requests
resp = requests.post('http://127.0.0.1:8000/login/', {'username': 'akshar', 'password': 'abc'})
print resp.status_code
200 #output
print resp.content
'Logged In' #output
cookies = dict(sessionid=resp.cookies.get('sessionid'))
print cookies
{'sessionid': '1fe38ea7b22b4d4f8d1b391e1ea816c0'} #output
response_two = requests.get('http://127.0.0.1:8000/getAllTracks/', cookies=cookies)
Notice that we pass cookies using cookies keyword argument
print response_two.status_code
200 #output
print response_two.content
'Authenticated user' #output
So, our request.user.is_authenticated() worked properly.
response_three = requests.get('http://127.0.0.1:8000/hogwarts/getAllTracks/')
Notice we do not pass the cookies here.
print response_three.content
'Non Authenticated user' #output
I guess, auth keyword for Requests enables HTTP Basic authentication which is not what is used in Django. You should make a POST request to login url of your project with username and password provided in POST data, after that your Requests instance will receive a session cookie with saved authentication data and will be able to do successful requests to auth-protected views.
Might be easier for you to just set a cookie on initial authentication, pass that back to the client, and then for future requests expect the client to send back that token in the headers, like so:
r = requests.post('http://127.0.0.1:8000', auth=(UN, PW))
self.token = r.cookies['token']
self.headers = {'token': token}
and then in further calls you could, assuming you're in the same class, just do:
r = requests.post('http://127.0.0.1:8000/getAllTracks', headers=self.headers)