Skip to content
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

Merged
merged 18 commits into from
Feb 14, 2018
Merged

Openid connect support (fixes #939) #1425

merged 18 commits into from
Feb 14, 2018

Conversation

leplatrem
Copy link
Contributor

@leplatrem leplatrem commented Dec 17, 2017

Fixes #939

  • Add docs
  • Move to plugins folder
  • Choose appropriate settings name
  • Add tests
  • Publish demo somewhere?
  • Create issue for kinto-admin support
  • Add mention in Github tutorial ? ref The Github oauth tutorial is not OpenID Connect compliant. #508
  • Polish OpenAPI spec
  • Reject scope that does not contain email if the configured userid_field is email
  • Implement the scope filtering as we did for kinto-fxa

We could make this piece of code work with both Auth0 and Firefox Accounts \o/

@rfk f?

@leplatrem leplatrem force-pushed the 939-openid-connect branch 2 times, most recently from 0ce4850 to d9db8ba Compare December 17, 2017 00:59
@rfk
Copy link

rfk commented Dec 20, 2017

We could make this piece of code work with both Auth0 and Firefox Accounts \o/

FWIW I'm a strong 👍 to this goal :-)

return None

try:
# Bearer+OIDC id_token=jwt, access_token=bearer
Copy link

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.

Copy link
Member

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.

Copy link
Member

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?

Copy link

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.

Copy link

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?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

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?

Copy link
Member

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...

Copy link
Member

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?

@leplatrem
Copy link
Contributor Author

@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}

@cfguimaraes
Copy link

@leplatrem thank you for the mention, I will try this on Codenvy

@almet
Copy link
Member

almet commented Jan 26, 2018

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 :-)

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"]
Copy link

@cfguimaraes cfguimaraes Jan 26, 2018

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

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.

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.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Status update:

  1. 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
  2. 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 the JWT, also, @leplatrem fixed an issue and make a PR to address an error on at_hashclaim being required, but accordingly to OIDC that is not required at all
  3. 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.

if access_token is None:
options = {"verify_at_hash": False}

payload = jwt.decode(id_token, rsa_key, algorithms=["RS256"],

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

Copy link
Contributor Author

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 :)

@cfguimaraes
Copy link

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.

@cfguimaraes
Copy link

cfguimaraes commented Feb 3, 2018

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.
Sadly I don't have many use cases to test, but Authentication is working fine, with a valid acess_token and Forbidden with an invalid JWT.

If there is something you'll want I do, I will be happy to help @leplatrem.

@leplatrem
Copy link
Contributor Author

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...

However, the code looks for a Bearer+OIDC Authorization Header, I couldn't find a place where this Header is described, only Bearer token.

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 Bearer and make it optional in settings to customize it. If you thought that was confusing, it's a sign :)

Notes:
https://developer.github.com/apps/building-oauth-apps/

@leplatrem
Copy link
Contributor Author

I made some changes to this branch. The tests will fail. The audience setting was renamed to client_id.

I made it work with Google Accounts, this is the frontend demo that I wrote:
https://github.com/leplatrem/kinto-oidc-demo/

It's still Work-In-Progress though.

@cfguimaraes
Copy link

I really think that a Bearer token as proposed is the best as default.
If one wants a different behavior that could configure that, I think that a tool has to be standard compatible, those that are exceptions have to write code for exception, IMO.

@Natim
Copy link
Member

Natim commented Feb 6, 2018

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 Bearer+OIDC realm then it doesn't change anything to write Bearer+OIDC rather than Bearer.

Plus this let you configure Kinto with multiple auth providers without messing around with configuration.

@leplatrem
Copy link
Contributor Author

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

return None

try:
# Bearer+OIDC id_token=jwt, access_token=bearer
Copy link
Member

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

Copy link
Contributor Author

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.

@cfguimaraes
Copy link

cfguimaraes commented Feb 6, 2018

@Natim, I like standard things, don't you think that being standard this tool will help much more developers at all?
In OIDC Core, token response section says

The OAuth 2.0 token_type response parameter value MUST be Bearer, as specified in OAuth 2.0 Bearer Token Usage [RFC6750], unless another Token Type has been negotiated with the Client

I think, having a non-standard Header like Bearer+OIDC will force 2 implementations, one for tools that accept content negotiation, and another for standard tools only. In the first case we will force developers to write and manage more code, to accept negotiation with Kinto endpoint, and to access other API's that are standard compatible only sending a Bearer jwt.
I as a developer don't like how this sounds, if I need a non-standard solution just implementing an interface and settings in kinto.ini (configuration file) will make me much happier.

@Natim
Copy link
Member

Natim commented Feb 6, 2018

unless another Token Type has been negotiated with the Client

That's my point.

I think, having a non-standard Header like Bearer+OIDC will force 2 implementations

I don't think so.

I as a developer don't like how this sounds, if I need a non-standard solution just implementing an interface and settings in kinto.ini (configuration file) will make me much happier.

I agree with you here.

@cfguimaraes
Copy link

I don't think so.

Could you say more about how you see this?
I still see 2 implementations, in this case, one for Bearer (standard), and another for any non-standard header (as any developer can extend kinto) like Bearer+OIDC as invented for @leplatrem in her code.

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 Bearer+OIDC token or change the code to expect a Bearer.
I've made the first because I'm writing request in a REST plugin, but when I've made an app to make tests with Angular the tools I used sends standard Bearer token. I don't investigate how to extend the js lib I used to send a custom Header, but I really, don't wish to do that.
And is this I'm saying, if we'll deliver a built-in plugin with Kinto, this SHOULD be OIDC standard compliant, this way we can bring a more convenient tool for developers.
If one needs a different solution than that, this already will have to write custom code, so is acceptable.

What you think about this?

@Natim
Copy link
Member

Natim commented Feb 6, 2018

Everything is the same (Bearer token, JWT). Only the Realm that has got a suffix when you call Kinto.
You can use the same lib, you can use everything that already exists around OIDC, grab the access_token and then when the clients call Kinto it will use the proper OIDC plugin realm.
That's why I don't think it needs another custom implementation.

I've made the first because I'm writing request in a REST plugin, but when I've made an app to make tests with Angular the tools I used sends standard Bearer token.

Then maybe I am wrong. Can you show me how you use your plugin?

leplatrem added a commit to leplatrem/kinto-oidc-demo that referenced this pull request Feb 8, 2018
@leplatrem
Copy link
Contributor Author

I reworked the settings part, it now supports multiple OpenID policies, and I updated the demo.
I drafted some docs as well.

At this point I would take as much feedback as possible :)

.. code-block:: ini

# User ID field name (Default: `sub`)
multiauth.policy.oidc.userid_field = email
Copy link
Member

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?

@leplatrem leplatrem changed the title [WIP] Openid connect support (fixes #939) Openid connect support (fixes #939) Feb 9, 2018
@leplatrem
Copy link
Contributor Author

leplatrem commented Feb 9, 2018

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» :)

@leplatrem
Copy link
Contributor Author

@Natim final r?

@almet
Copy link
Member

almet commented Feb 13, 2018

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» :)

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!)

@Natim
Copy link
Member

Natim commented Feb 13, 2018

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.

@leplatrem
Copy link
Contributor Author

I think it is good enough as it is to be merge right away

I'll create the issue once approved and merged :)

return None

try:
# Bearer+OIDC id_token=jwt, access_token=bearer
Copy link
Member

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?

@Natim
Copy link
Member

Natim commented Feb 14, 2018

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.

@leplatrem
Copy link
Contributor Author

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.

Our original use case is to obtain different userids depending on scope (to isolate data between apps basically).
What if we just define two clients IDs and define two policies?

    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 notes:abc on one side, and lockbox:abc on the other side... The IdP could restrict the access to one or another with the callback/redirect URIs settings.

I believe we should remove the custom authentication flow using id_token.

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.

@Natim
Copy link
Member

Natim commented Feb 14, 2018

What if we just define two clients IDs and define two policies?

The issue is that in that case the bearer_token will be validated in both case right?

@leplatrem
Copy link
Contributor Author

leplatrem commented Feb 14, 2018

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

Copy link
Member

@Natim Natim left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks

@leplatrem leplatrem merged commit 3b6556a into master Feb 14, 2018
@leplatrem leplatrem deleted the 939-openid-connect branch February 14, 2018 14:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants