restful service + auth on same application/ different controllers = gives Not authorized message

419 views
Skip to first unread message

Leandro Sebastian Salgueiro

unread,
Nov 13, 2017, 9:14:00 AM11/13/17
to web2py-users
HI,

I have two controllers on the same app:

TestApp
|
|---default.py
|---api.py

api is a restful service that will call other services. For security reasons I would like that all call to these services are passed by the api restful. (it will work like a proxy in this case)

I did try the following :

in default.py :

@auth.requires_login()
def index():
import requests
json = requests.get(URL('api', 'apps', host=True))
return {"json": json.content}

in api.py:

import requests
apps_url = 'http://localhost:8091/apps'

@auth.requires_login()
@request.restful()
def apps():
response.view = 'generic.json'
def GET(*args,**vars):
r = requests.get(apps_url)
return r
return dict(GET=GET)

If i test this without the api's login decorator everything works fine. However I can access this restful from anywhere else... 
I added then the requires_login to api controller and then i test both URLs independently from browser, it works ok (login to web2py ->  go to /api/apps -> get my results) however if I do the GET request using requests.get from default controller i get a Non Authorized message and redirect to login form.

what i'm missing here? i thought that if I was in the same app, auth session would be shared among different controllers... 

any hint on this would be the most welcomed..
Thanks in advanced.
Leandro



Carlos A. Armenta Castro

unread,
Nov 14, 2017, 12:05:36 PM11/14/17
to web2py-users
Hola Leandro, te escribo en español porque al ver tu nombre me parece que hablas castellano, corrigeme si me equivoco y te lo escribo en ingles, 



El lunes, 13 de noviembre de 2017, 7:14:00 (UTC-7), Leandro Sebastian Salgueiro escribió:

I added then the requires_login to api controller and then i test both URLs independently from browser, it works ok (login to web2py ->  go to /api/apps -> get my results) however if I do the GET request using requests.get from default controller i get a Non Authorized message and redirect to login form.

En este caso en tu código:
def index():
import requests
json = requests.get(URL('api', 'apps', host=True))

Lo que haces es iniciar otra sesión en tu misma APP pero no le estás enviando las credenciales para el Login, yo entiendo que cada ves que invocas a requests creas una nueva sesión entonces tienes que hacer Login cada vez.

Me parece un poco extraño lo que haces en tu código porque si ya estás firmado no se porque buscas firmarte nuevamente. Te recomiendo abordar el problema de una manera distinta. Web2Py es Roca Solida en cuando a seguridad, no deberías preocuparte por problemas de seguridad una vez que ya estás firmado en tu App.

Si necesitas seguridad Extra para tu APP, entonces te recomiendo usar JWT Tokens con Web2Py http://web2py.readthedocs.io/en/latest/tools.html

jwt()[source]

To use JWT authentication: 1) instantiate auth with:

auth = Auth(db, jwt = {'secret_key':'secret'})

where ‘secret’ is your own secret string.

  1. Decorate functions that require login but should accept the JWT token credentials:

    @auth.allows_jwt()
    @auth.requires_login()
    def myapi(): return 'hello %s' % auth.user.email
    

Notice jwt is allowed but not required. if user is logged in, myapi is accessible.

  1. Use it!

Now API users can obtain a token with

(returns json object with a token attribute) API users can refresh an existing token with

they can authenticate themselves when calling http:/.../myapi by injecting a header

Authorization: Bearer <the jwt token>
Saludos y suerte con tu APP. 

Leandro Sebastian Salgueiro

unread,
Nov 16, 2017, 8:17:39 AM11/16/17
to web2py-users
Hola Carlos, efectivamente soy latino :-) ..

Muchas gracias por tu respuesta, me queda mucho mas claro lo que esta pasando...

estoy de acuerdo contigo, lo que trato de hacer es un poco (bastante) extraño, era una solucion provisoria hasta que pueda crear autorisaciones token en mis microservicios.. 

lo que dices de JWT es lo correcto y es lo que tenia pensado para mi solucion final... me parece que es mas facil hacer JWT que perder el tiempo a tratar de re-inventar la rueda :-)

saludos y gracias de nuevo

Leandro

Val K

unread,
Nov 20, 2017, 6:08:56 PM11/20/17
to web2py-users

Hi, you can use requests.Session:

#in default
session 
= requests.Session()
url_login = 'http://..../api/login.json'   
#requests.packages.urllib3.disable_warnings()  # - uncomment if you use a self-signed cert over https 
r = session.get(url_login, verify=True) #set verify=False if you use a self-signed cert over https

form = dict( username = 'user',   password = 'password')
r = session.post(url_login, data = form)
if r.status_code==200: #server OK
     response_data = json.loads(r.text)
     logged_in = 'logged_in' in response_data.keys()
         # if logged_in == True  -  session is authorized, so use  session.post/get ...  to request api
 

#in api 
@request.restful()
def login():
    response.view = 'generic.json'
   user = request.vars.username
   password = request.vars.password
      if auth.login_bare(user, password):
            return dict(logged_in = 'yes')

auth.requires_login()  redirects to login form, but it's redundant for api 
# instead of auth.requires_login() you can write your own simple decorator:
def api_requires_login(f):
    if auth.is_logged_in():
        return f
    raise HTTP(401) # or return something








Dave S

unread,
Nov 20, 2017, 10:35:27 PM11/20/17
to web2py-users


On Monday, November 20, 2017 at 3:08:56 PM UTC-8, Val K wrote:

Hi, you can use requests.Session:

#in default
session 
= requests.Session()

session is an already-defined global.
 
url_login = 'http://..../api/login.json'   

Shouldn't you be using the URL helper?  For my setup, I tried 'URL("user/login",scheme="https", host=True)' since I don't have a second controller where I'm testing, and the default/user/login is the normal method for me.

#requests.packages.urllib3.disable_warnings()  # - uncomment if you use a self-signed cert over https 
r = session.get(url_login, verify=True) #set verify=False if you use a self-signed cert over https


I'm not sure about this.  As is, it produces a ticket for "get() takes no keyword arguments".  Taking out the verify, I get a result of 'None'.  That doesn't seem to be useful to me.

/dps

 

Dave S

unread,
Nov 20, 2017, 10:48:28 PM11/20/17
to web2py-users

You may be running into a twist that Anthony explained to me, where an API call's session doesn't last past the call (some qualifiers may be needed).   I'm not sure if that is true when the controller/function does an request "from the inside", but I would think about that.

I also think you might be better served using JWT for API authorization, and web2py has supported that for a while.  Look at the following thread for a little about using JWT, and for Anthony's comment about session lifetimes.
<URL:https://groups.google.com/d/topic/web2py/VMhvBLU0zW0/discussion>
For more on JWT, look at the references in that thread (another thread, and gluon/tools.py).

/dps

Val K

unread,
Nov 21, 2017, 1:55:02 AM11/21/17
to web...@googlegroups.com
As I see Sebastian uses requestS module, don't confuse with web2py request object. Yes  it's not a good idea to use 'session' as name

Anthony

unread,
Nov 21, 2017, 7:38:12 AM11/21/17
to web...@googlegroups.com

#requests.packages.urllib3.disable_warnings()  
# - uncomment if you use a self-signed cert over https 
r = session.get(url_login, verify=True) #set verify=False if you use a self-signed cert over https


I'm not sure about this.  As is, it produces a ticket for "get() takes no keyword arguments".  Taking out the verify, I get a result of 'None'.  That doesn't seem to be useful to me.

In the above code, session is an instance of the requests.Session class, and its .get() method does indeed take keyword arguments, as shown here: http://docs.python-requests.org/en/v1.0.4/user/advanced/#session-objects. The keyword arguments are actually passed to the requests object itself -- the "verify" keyword is documented here: http://docs.python-requests.org/en/master/user/advanced/#ssl-cert-verification.

Anthony

Anthony

unread,
Nov 21, 2017, 7:47:40 AM11/21/17
to web2py-users
What are you really trying to do? Is the API simply being called from the browser, or are other types of clients calling it? If that latter, you might look into using JWT auth, as session based authentication doesn't work well for non-browser clients unless you get them to maintain a session by passing the session cookie back and forth. If the only client is the browser, just have it make Ajax calls directly to the API endpoints -- no reason to pass requests through an endpoint in another controller.

Anthony

Leandro Sebastian Salgueiro

unread,
Nov 21, 2017, 8:10:28 AM11/21/17
to web2py-users
Thanks Anthony, All,

I know that what I'm trying to do here is kind of twisted ..

I have a quite complex structure of microservices on backend that needs to be accessed by Frontend. my final idea (at the end of the project) is to use JWT on every microservice but in the meanwhile (as i need a basic protection for beta testers) i was hoping to have a second controller working as a proxy and let web2py to handle auth.

in an ideal world :-) i was thinking that API will share auth with default controller, as it is part of the same app,  so if user is logged in it can use the API to make calls to the other endpoints... (in this way only web2py is exposed and i keep the other microservices protected inside the LAN). 
By the way when i try each controller from browser it works ok... the problem is the communication between controllers.. but after all these explanations i have a much better view of what is going on..

it seems now that my "quick" workaround was not that simple (unless there is a magical solution somewhere) and is easier to direclty implement JWT on all my microservices and frontend.

thanks a lot for all these comments...

BR

Leandro

Dave S

unread,
Nov 21, 2017, 8:52:24 AM11/21/17
to web2py-users

On Tuesday, November 21, 2017 at 5:10:28 AM UTC-8, Leandro Sebastian Salgueiro wrote:
 
it seems now that my "quick" workaround was not that simple (unless there is a magical solution somewhere) and is easier to direclty implement JWT on all my microservices and frontend.


Web2py does JWT fairly easily (for the experiments I've done so far), if it helps you do an alternative quick workaround.

/dps

Dave S

unread,
Nov 21, 2017, 8:53:35 AM11/21/17
to web2py-users
More proof that there's always more to learn.

Thanks.

/dps
 

黄祥

unread,
May 17, 2018, 11:30:36 AM5/17/18
to web2py-users
models/db.py
from gluon.tools import Auth, AuthJWT
auth = Auth(db, controller = 'default', host_names = configuration.get(configuration_env + '_' + 'auth.host') )

controllers/api.py
myjwt = AuthJWT(auth, secret_key = 'secret')

def login_and_take_token():
    return myjwt.jwt_token_manager()

@myjwt.allows_jwt()
@auth.requires_login()
def header_jwt():
    if not request.env.request_method == 'GET': raise HTTP(403)
    if auth.is_logged_in():
        table_name = request.args(0)
        id = request.args(1)

        if id.isdigit() and int(id) > 0:
            query = (db[table_name]['id'] == id)
        else:
            query = (db[table_name]['id'] > 0)

        rows = db(query).select().as_json()
        return rows
    raise HTTP(401)

"""
## Terminal using curl
# Token Generator
curl -X POST -d username=user -d password=password -i http://127.0.0.1:8000/test/api/login_and_take_token

# Auth with Token Only
curl --user user:password -H "Authorization: Bearer paste_jwt_token_here" http://127.0.0.1:8000/test/api/header_jwt/table/1
curl --user user:password -H "Authorization: Bearer paste_jwt_token_here" http://127.0.0.1:8000/test/api/header_jwt.json/table/1
"""

command :
curl -H "Authorization: Bearer paste_jwt_token_here" http://127.0.0.1:8000/test/api/header_jwt/table/1
result:
data shown without user credentials
expected result:
data not shown without user credentials

any idea? or is it normal because from code above i've used @auth.requires.login() even put the auth.is_logged_in() decorator?

thx and best regards,
stifan

Anthony

unread,
May 17, 2018, 4:16:29 PM5/17/18
to web2py-users
command :
curl -H "Authorization: Bearer paste_jwt_token_here" http://127.0.0.1:8000/test/api/header_jwt/table/1
result:
data shown without user credentials
expected result:
data not shown without user credentials

any idea? or is it normal because from code above i've used @auth.requires.login() even put the auth.is_logged_in() decorator?

Credentials are needed to get a token, not to use the token. There would be no point to the token if it required the credentials to be provided and verified along with it. The token itself serves as verification that the user is authorized.

Anthony

黄祥

unread,
May 17, 2018, 5:57:13 PM5/17/18
to web2py-users
it's clear enough, thx anthony
curl -X GET --user user:password -i http://127.0.0.1:8000/test/api/header_jwt/table/1
result:
Invalid JWT header

result after login in browser:
data shown

is it normal? or did i misunderstand the concept of curl and open the url in browser that have decorator allows_jwt() and requires_login() ?

best regards,
stifan

Anthony

unread,
May 17, 2018, 7:48:41 PM5/17/18
to web2py-users
allows_jwt means JWT is allowed, not that it is required. When you open the URL in the browser, you will have access as long as you are logged in in the browser -- JWT is irrelevant in that context.

Anthony

黄祥

unread,
May 17, 2018, 8:08:18 PM5/17/18
to web2py-users
pretty clear, thx anthony

best regards,
stifan

Anthony

unread,
May 18, 2018, 10:47:22 AM5/18/18
to web2py-users
allows_jwt means JWT is allowed, not that it is required. When you open the URL in the browser, you will have access as long as you are logged in in the browser -- JWT is irrelevant in that context.

Just to clarify, you can use JWT for authentication even from the browser, but given your current setup, the standard cookie-based authentication is still functioning.

Anthony

黄祥

unread,
May 18, 2018, 11:01:24 AM5/18/18
to web2py-users
allows_jwt means JWT is allowed, not that it is required. When you open the URL in the browser, you will have access as long as you are logged in in the browser -- JWT is irrelevant in that context.

Just to clarify, you can use JWT for authentication even from the browser, but given your current setup, the standard cookie-based authentication is still functioning.

# Auth with Token Only
Reply all
Reply to author
Forward
0 new messages