-
Notifications
You must be signed in to change notification settings - Fork 422
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Openid connect support (fixes #939) #1425
Conversation
0ce4850
to
d9db8ba
Compare
FWIW I'm a strong 👍 to this goal :-) |
kinto/core/authentication.py
Outdated
return None | ||
|
||
try: | ||
# Bearer+OIDC id_token=jwt, access_token=bearer |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Out of curiosity, is this approach based on some prior art, or your own scheme? I'm not sure how well it will interact with refresh tokens, which would allow you to generate a fresh access_token that's not bound to the id_token.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is our own scheme. We asked ourselves a lot of question about that.
Looking at the Auth0 documentation is seems that the only way to verify an access_token is to use the id_token. (at_hash value) but it doesn't seems to be the way of doing it with Firefox Account where we would most likely use the /verify
endpoint.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My understanding of OAuth was that sending the access_token should be enough but then how do you validate it with Auth0? How does services usually work with id_token and access_token ? Do they send the id_token?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My understanding of OAuth was that sending the access_token should be enough
That's my understanding as well. The id_token is a point-in-time confirmation that we successfully authenticated a particular user, and the access_token is an ongoing grant of authorization to do a particular thing. I don't know much about Auth0, but having to send both on every request doesn't mesh with my expectations of OIDC.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at the Auth0 documentation is seems that the only way to verify an access_token is to use the id_token
Can you please drop a link to this for my reference?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was it: https://auth0.com/docs/api-auth/tutorials/verify-access-token
But this is what I also found: https://auth0.com/docs/api-auth/intro#calling-apis-with-access-tokens
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So maybe the access_token is also a JWT that self validates?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The problem is that if you look at this table: https://auth0.com/docs/api-auth/intro#how-to-use-the-new-flows
You see that tokeninfo is disabled...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we rely on userinfo call instead?
@cfguimaraes in order to try this branch with Auth0, you can use these settings: multiauth.policies = oidc
multiauth.policy.oidc.use = kinto.core.authentication.OpenIDConnectPolicy
oidc.issuer_url = https://{yours}.auth0.com
oidc.audience = {api-id} |
@leplatrem thank you for the mention, I will try this on Codenvy |
This seems great, thanks for working on it! Being able to use Openid connect will be a lot useful, so we can ditch basic auth default mechanism :-) |
kinto/core/authentication.py
Outdated
parts = payload.split(',') | ||
credentials = {k: v for k, v in [p.strip().split("=") for p in parts]} | ||
id_token = credentials["id_token"] | ||
access_token = credentials["access_token"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are keys ["id_token", "acess_token"] mandatory in JWT from an OIDC provider?
I've created a non-interactive client on Auth0 dashboard, from the APIs gotten the JWT token using curl, however, I always got HTTP 401 Unauthorized from Kinto.
{
"iss": "https://tenant.auth0.com/",
"sub": "key@clients",
"aud": "https://tenant.auth0.com/api/v2/",
"iat": 1516993206,
"exp": 1517079606,
"azp": "gaJ9QUArBJTPpL76SYV8jDhqu9Cztq93",
"gty": "client-credentials"
}
The code above is the Payload extracted from JWT.io site
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Accordingly to section 2 from Open ID Core the keys required are all right.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My bad, I'm testing all day with a non-interactive client from Auth0, the purpose of that is to grant authorization to scripts, in a server-server communication channel.
Tomorrow I will plug Auth0 lock (login) and update this topic with discovers, I'm really happy with how simples this code are at the same time how great are thanks to OIDC discovery.
Thanks to @leplatrem too for spend resources on this code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Status update:
- First of all, I become testing this in an Ionic project, already configured with Auth0, but when trying to integrate with kinto.js I don't know why, but sadly doesn't work
- Download quick start from Auth0's GitHub samples, inspecting the code on this branch I've seen that this is relying on python-jose for authentication, specifically to
decode
theJWT
, also, @leplatrem fixed an issue and make a PR to address an error onat_hash
claim being required, but accordingly to OIDC that is not required at all - I've forked the @leplatrem repo from python-jose and build the branch with her contributions to generate a wheel (whl) and installed it with pip3, however, I'm always getting an error from Authentication.py
Incorrect claims, please check the audience and issuer: **at_hash claim missing from token**.
I think that the local installation of python-jose (whl) is not being reflected on kinto source, can someone give me a help, I don't know what I have to do to make the decode function of python-jose work with enhanced code from @leplatrem in order to advance in these tests.
The good news is that after all, we already have a sample demonstrating how to integrate OIDC compliant like Auth0 to kinto.
kinto/core/authentication.py
Outdated
if access_token is None: | ||
options = {"verify_at_hash": False} | ||
|
||
payload = jwt.decode(id_token, rsa_key, algorithms=["RS256"], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've logged the code and verified that the error at_hash claim missing from token
is being raised from this function.
I cloned (https://github.com/leplatrem/python-jose/tree/75-at_hash-optional)[leplatrem python-jose] implementation that is supposed to address the issue in order to test, but I don't know if the changes on code is being reflected on kinto source
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry @cfguimaraes for the delay...
Basically you should be able to apply the fork with:
$ cd kinto/
$ source .venv/bin/activate
$ pip install -e <path-to-python-jose-fork>
But according to the code above if you provide a JWT but no access token, then this hash is not verified.
The status of this branch is still very rough, I want to land it ASAP, we'll need it in our production stacks at Mozilla :)
d9db8ba
to
ce32ca4
Compare
Good news, working today in this feature I could connect with Auth0 OIDC Conformant JWT Token. However, the code looks for a Bearer+OIDC Authorization Header, I couldn't find a place where this Header is described, only Bearer token. The code in the branch doesn't seem to work properly, I've needed to fork python-jose implementation of the at_hash issue of leplatrem, after that all works fine. I will clear the local code, and make new tests to ensure the python-jose code has a bug or not in the current version of Kinto. In 4 hours I'll make a report. |
I've cleaned local code, make a pull and update the code to reflect the locations for the plugin and all work fine, without the code on python-jose. If there is something you'll want I do, I will be happy to help @leplatrem. |
Thanks @cfguimaraes for your detailed feedback! Next step on my side is to write a little single page app demo (with Github auth or something) and update the docs / tutorial...
We invented it with @Natim :) Since Kinto can have several authentication policies enabled, with several ones that use bearer tokens, we wanted to have a special one. Maybe we could default it to just
|
I made some changes to this branch. The tests will fail. The I made it work with Google Accounts, this is the frontend demo that I wrote: It's still Work-In-Progress though. |
I really think that a Bearer token as proposed is the best as default. |
To be honest I am not quite sure about that postulate. When using the bearer token with Kinto you still have a to build the Authorization header by hand. If you know that Kinto expect a Plus this let you configure Kinto with multiple auth providers without messing around with configuration. |
Honestly, having multiple authentication policies configured is not the most common use-case |
kinto/plugins/openid/__init__.py
Outdated
return None | ||
|
||
try: | ||
# Bearer+OIDC id_token=jwt, access_token=bearer |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove support for ID_Token (which is used by the frontend), on the backend we want to use access/bearer token
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The thing is that we can have access tokens in JWT format. I haven't looked at those yet.
Also, using id tokens saves a round-trip to the Identity Provider. With the inconvenient that their validity remains unquestioned until they expire.
@Natim, I like standard things, don't you think that being standard this tool will help much more developers at all?
I think, having a non-standard Header like |
That's my point.
I don't think so.
I agree with you here. |
Could you say more about how you see this? I've made a login in an Auth0 OIDC compliant client, sending a compliant acess_token Authorization heared as a Bearer jwt I've been Unauthorized, so I've to send a Kinto compliant What you think about this? |
Everything is the same (Bearer token, JWT). Only the Realm that has got a suffix when you call Kinto.
Then maybe I am wrong. Can you show me how you use your plugin? |
I reworked the settings part, it now supports multiple OpenID policies, and I updated the demo. At this point I would take as much feedback as possible :) |
docs/configuration/settings.rst
Outdated
.. code-block:: ini | ||
|
||
# User ID field name (Default: `sub`) | ||
multiauth.policy.oidc.userid_field = email |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we replace oidc by google here?
The code is ready to be thoroughly reviewed/merged. Also, I would also really appreciate if someone could test this: https://github.com/leplatrem/kinto-oidc-demo Now the question is: should we do the scope filtering feature in this PR as well as revamp the whole authentication documentation? My take is «no» :) |
@Natim final r? |
That's the easy way, for sure ! But the risk is that it will stay as it is. I remember that this auth documentation have had to be revamped for some time now and it's still not the case. My take would be to have documentation before merging this ;) (but, it's easy to say when you're not the one doing it!) |
I think the authentication documentation already have been revamp to use account rather than the dummy Basic Auth, I don't think it is related to openid anyway but more to the account plugin so another pull-request will be fine. I think it is good enough as it is to be merge right away, we should create the issue regarding documentation and scopes management once we know how we are supposed to handle it with Auth0. |
I'll create the issue once approved and merged :) |
kinto/plugins/openid/__init__.py
Outdated
return None | ||
|
||
try: | ||
# Bearer+OIDC id_token=jwt, access_token=bearer |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this on purpose? I though we said that we would only send Bearer token and not id_tokens anymore?
Since OpenID is about Authentication and Oauth about Authorization, I believe it is fine not validating scopes here and using the userinfo_endpoint to validate bearer tokens. In the meantime I believe we should remove the custom authentication flow using id_token. |
Our original use case is to obtain different userids depending on scope (to isolate data between apps basically). multiauth.policy.notes.issuer = https://accounts.google.com
multiauth.policy.notes.client_id = YYYYYYYY.apps.googleusercontent.com
multiauth.policy.notes.client_secret = UAlL-YYYYYYYY
multiauth.policy.lockbox.issuer = https://accounts.google.com
multiauth.policy.lockbox.client_id = XXXXXX.apps.googleusercontent.com
multiauth.policy.lockbox.client_secret = UAlL-XXXXXX The user ID will be
I don't know, using ID tokens saves a round trip to the IdP. But yes, it would make everything simpler indeed. I wish I had an example of flow where we obtain JWT access tokens, but I don't. Yeah, you're right, let's make it super stupid and simple: just access tokens. |
The issue is that in that case the bearer_token will be validated in both case right? |
If it's for the second policy, yes. Two round trips to the IdP |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks
Fixes #939
email
if the configured userid_field is emailWe could make this piece of code work with both Auth0 and Firefox Accounts \o/
@rfk f?