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

Add an OAuth2 extension #3078

Merged
merged 1 commit into from
Jul 30, 2019
Merged

Conversation

loicmathieu
Copy link
Contributor

This PR add an OAuth 2 extentions, based on wildfly-elytron-realm-token that allow to implements a simple OAUth2 authentication flow, without JWT nor Keyclock.

It covers the case of classic OAuth authentication flow like client_credential or resource_owner_password without OIDC.

@loicmathieu
Copy link
Contributor Author

ping @sberyozkin can you review it and give some feedback.

If it is decided to be included in Quarkus, I will need to add documentation & integration tests. But I don't want to work on these if it will not be incuded ...

@emmanuelbernard emmanuelbernard requested a review from starksm64 July 3, 2019 09:28
@emmanuelbernard
Copy link
Member

CC @sebastienblanc too

@sberyozkin
Copy link
Member

sberyozkin commented Jul 3, 2019

Hi @loicmathieu, All
IMHO it would be a worthy feature to have. I'd suggest avoiding the oauth2 qualifier in the extension name and use realm-token instead to avoid some possible ambiguities (however it is not a big deal, if -oauth2 works for others then it would be OK for me as well, since OIDC is built on top of OAuth2, which may suggest that this extension can take care of all kind of tokens).

As I have mentioned earlier, smallrye-jwt should have a token introspection option available as well but at this moment of time it is only a plan which would also require refactoring the Quarkus smallrye-jwt extension to use the factories and the users would be expected to have a service loader resource pointing to the introspection endpoint factory and some details are not yet clear. As such I don't think it is a good reason not to accept this PR. Instead, combining the extension proposed by this PR and smallrye-jwt to have the introspected tokens exposed as MP JWT tokens can be a viable alternative and consistent with the similar idea for combining KC adapter and smallrye-jwt.

CC-ing @starksm64 as well.

@loicmathieu loicmathieu force-pushed the feat/oauth_provider branch from 4b119ca to 024fd7d Compare July 3, 2019 15:10
@sberyozkin
Copy link
Member

sberyozkin commented Jul 4, 2019

@starksm64, @emmanuelbernard, @gsmet, @sebastienblanc Can you please indicate to @loicmathieu if the PR can make it. I recommend for it to go in (once the tests are in and some minor tweaks are completed), please decide

@emmanuelbernard
Copy link
Member

emmanuelbernard commented Jul 4, 2019 via email

@loicmathieu loicmathieu force-pushed the feat/oauth_provider branch from 76f6ee5 to 2eaf9e8 Compare July 4, 2019 14:36
@sberyozkin
Copy link
Member

@emmanuelbernard np :-), all this PR does is it sends (indirectly) a simple HTTP POST request to the token introspection endpoint which returns a simple JSON response with the token properties, and this PR then takes the name and roles (from the usual scope property the name can be customized in the future) and populates a Principal and that is it. Should make quite a few users happy for whom the KC adapter does not suit

@sberyozkin
Copy link
Member

sberyozkin commented Jul 4, 2019

@loicmathieu Hi, can you please spend a bit of time on the integration test (not sure, may be you can check what is happening with the KC integration tests and just use the KC server to introspect the (JWT) token ? It does not really matter it won't be a binary one, the concept is the same).

@loicmathieu
Copy link
Contributor Author

@sberyozkin I will work on it tomorrow.
Is it OK to use wiremock to mock an introspection endpoint?

The Keycloack integration test are skipped, I assume because they need a Keycloack installed ;), so I prefer to mock the response of the introspection endpoint, it's way more easy for me.

@sberyozkin
Copy link
Member

@loicmathieu sure, mocking it would be fine I guess for a start, may be copy a JSON response fragment from the introspection spec text (or use whatever you prefer :-)).
FYI, I've opened #3092. @pedroigor @sebastienblanc hope you don't mind :-)

@loicmathieu
Copy link
Contributor Author

@sberyozkin I added an integration test ... and fixes one bug that the test discover ;)
I could write a guide next week if it's a GO for the extention.

@sebastienblanc
Copy link
Contributor

I need to find some time to review this. I understand the need of having an OAuth2 extension but this is not going to ease the consolidation of the security story ;)
I see also a lot of duplicate code with the keycloak-undertow adapter .
@loicmathieu Is it correct that what you really need is an extension that can validate an opaque token using an introspection endpoint ? OIDC does not make it mandatory to have an JWT token for the access token , although Keycloak does it this is something we should change on our side to support this.
I would also really like the feedback of @pedroigor and @patriot1burke on this.

@sberyozkin
Copy link
Member

sberyozkin commented Jul 5, 2019

@sebastienblanc Expecting Keycloak to start supporting the binary tokens is unrealistic IMHO in the short/medium term, though would be glad to be proven wrong. The reality is the Keycloak adapter can not handle all the tokens in the world :-) and it is not a limitation of the KC in anyway.

By the way, @loicmathieu has mentioned that this is not an OIDC flow he is dealing with, but a client credentials or similar with the tokens possibly being issued by the home-grown or some other 3rd party Oauth2 provider.

I agree there is a certain overlap and I've talked about it above with the respect the similar option be eventually (possibly) offered by smallrye-jwt, but I honestly don't think it conflicts with the higher-end KC adapter dealing with the variety of OIDC flows.

@pedroigor
Copy link
Contributor

pedroigor commented Jul 5, 2019

I agree with @sberyozkin. The extension is basically leveraging Elytron Token Realm and OAuth2 Token Validator. The latter enables OAuth2 support to the token realm so that access tokens sent as a bearer can be validated using a token introspection endpoint.

Spec-wise, OAuth2 access tokens are opaque and need a call to the introspection endpoint in order to check their validity as well as the claims within the token, mainly the scopes granted to a client acting on behalf of the user. Just like in Elytron the extension provides a pure OAuth2 bearer authorization. On the other hand, the Keycloak extension assumes that access tokens are JWTs and validation is only performed locally (no call to introspection endpoint).

The Keycloak adapter also provides specific features that are implementation specific. So, as we discussed before, it is mainly targeted for those using Keycloak as OIDC OP, providing the same experience in terms of functionalities for those coming from Spring or Wildfly.

In our last team meeting, Stian mentioned that we should probably drop our adapter in favor of standard implementations of the specs that Keycloak support. So, I can think of this extension (or improvements to smallrye-jwt) as an important move in this direction.

Regarding the capabilities provided by this extension, I think we could also consider:

*
* @return
*/
@BuildStep(providesCapabilities = "io.quarkus.quarkus-elytron-security-oauth2")
Copy link
Member

Choose a reason for hiding this comment

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

@loicmathieu It should be a package name, see the Capabilities sub section

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done, I use io.quarkus.elytron.security.oauth2

@sberyozkin
Copy link
Member

sberyozkin commented Jul 17, 2019

@loicmathieu one thing I'd like to experiment with once this PR makes it, is to update this extension to look for something like PrincipalProvider and if it is found then pass the Elytron Attributes to it and PrincipalProvider will return a Principal which will have to be used instead of the default one offered by this extension. This way smallrye-jwt would complement this extension and make the tokens obtained by it exposed via Mp JWT API. (and repeat the same for the KC extension)

@pedroigor
Copy link
Contributor

@sberyozkin I also think is a good addition. Although, like you and @sebastienblanc said, not really helps with a unification of the security story in Quarkus.

From a user perspective, the extension provides an easy and nice way to enable a standard-based OAuth2 Bearer Authorization. There are more to consider but I think they could be part of the second round of changes.

@loicmathieu
Copy link
Contributor Author

@sberyozkin thanks for your support :). After this first version merged, I will opens followup issues for RoleDecoder, native SSL support and PrincipalProvider.

@sberyozkin
Copy link
Member

@pedroigor thanks, I was less concerned about the unification at this point of time :-) as it seems this extension fits its own niche as we discussed earlier.

Please also see my last comment to @loicmathieu, I feel like this path will help us unify things, example, this extension + smallrye-jwt, or KC + smallrye-jwt, the only other thing would be needed for smallrye-jwt to check the BuildStep capabilities (to determine if either KC or this extension has been loaded) and offer an Attributes to JsonWebToken principal converter, the KC or this extension would have the populated Attributes.
Something like that :-), I'm sure we will figure out the minor details when we start dealing with it... I'll certainly keep you all up to date once I'll start looking at it from the smallrye-jwt point of view
thanks

@pedroigor
Copy link
Contributor

Sounds like a good plan. Please, let me know if I can help with something.

@sberyozkin
Copy link
Member

@pedroigor sure, thanks

Copy link
Member

@gsmet gsmet left a comment

Choose a reason for hiding this comment

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

This needs to be squashed before merging.

Disclaimer: I haven't read all the code as I'm not really versed in the security layer but have reviewed the doc and made a scan through the code.

I haven't validated the architecture either as the others have the vision of what we want for security.


include::./attributes.adoc[]
:extension-name: Elytron Security OAuth2
:oauth2-token: OAuth2 tokens
Copy link
Member

Choose a reason for hiding this comment

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

Hmmm, what's the purpose of this one?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I create this guide based on the JWT one and kept the variables in place, I can remove them if you want.

Copy link
Member

Choose a reason for hiding this comment

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

We can keep the extension name but really oauth2-token should go away.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

docs/src/main/asciidoc/oauth2-guide.adoc Outdated Show resolved Hide resolved
[cols="<m,<m,<2",options="header"]
|===
|Property Name|Default|Description
|quarkus.oauth2.enabled|true|Determine if the OAuth2 extension is enabled. Enabled by default if you include the elytron-security-oauth2 dependency, so this would be used to disable it.
Copy link
Member

Choose a reason for hiding this comment

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

backticks around the dependency name would be nice.

Maybe also around the properties, depending on how it is done in the other guides.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done.

backticks around properties when use a table is not the common usage on the other guides.


This command generates the Maven project with a REST endpoint and imports the `elytron-security-oauth2` extension, which includes the {auth2-token} support.

If you don't want to use the maven plugin, you can just include the dependency in your pom.xml:
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
If you don't want to use the maven plugin, you can just include the dependency in your pom.xml:
If you don't want to use the Maven plugin, you can just include the dependency in your pom.xml:

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done


* user name is anonymous
* isSecure is false as https is not used
* authScheme is null
Copy link
Member

Choose a reason for hiding this comment

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

Some backticks around isSecure and authScheme would be nice.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

credential, exchange.getRequestPath(), account.getRoles());
return AuthenticationMechanismOutcome.AUTHENTICATED;
} else {
UndertowLogger.SECURITY_LOGGER.info("Failed to authenticate Oauth2 bearer token");
Copy link
Member

Choose a reason for hiding this comment

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

Is it an info, an error, or something that can happen in a normal behavior?

From the code, it looks like a normal behavior so we shouldn't log anything.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is the same code as in the JWT extension, I prefere to keep it consistent.

Authentication failures are normal, but it's important to be able to know that a failure occurs so I always love to see those logs. A user can disabled them by raising the level to WARN.

In fact, I found the current extension not loggin enought in case of authentication failures (for example, if it cannot access the introspection endpoint there is no error on the log). Maybe I will revisit it later to raise more error (but again it's the same as the JWT extension)

credential, exchange.getRequestPath(), account.getRoles());
return AuthenticationMechanismOutcome.AUTHENTICATED;
} else {
UndertowLogger.SECURITY_LOGGER.info("Failed to authenticate Oauth2 bearer token");
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
UndertowLogger.SECURITY_LOGGER.info("Failed to authenticate Oauth2 bearer token");
UndertowLogger.SECURITY_LOGGER.info("Failed to authenticate OAuth2 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.

done

return AuthenticationMechanismOutcome.NOT_AUTHENTICATED;
}
} catch (Exception e) {
UndertowLogger.SECURITY_LOGGER.infof(e, "Failed to validate Oauth2 bearer token");
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
UndertowLogger.SECURITY_LOGGER.infof(e, "Failed to validate Oauth2 bearer token");
UndertowLogger.SECURITY_LOGGER.infof(e, "Failed to validate OAuth2 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.

done

<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlet</artifactId>
<version>9.4.17.v20190418</version><!-- or 9.4.15.v20190215 -->
Copy link
Member

Choose a reason for hiding this comment

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

Why do we want Jetty while we have Undertow?

And in any case, it should be in the bom.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is a used by wiremock and there is a dependency management issue in wiremock itself. I can open an issue on wiremock and reference it here to be able to remove this dependency management override later

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I openned an issue and add the link as a comment in the pom

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 issue is not on wiremock, it's because we define some jetty dependencies in the bom and not all. I added the missing one.

<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock-jre8</artifactId>
<version>2.23.2</version><!-- TODO add it to parent POM ?-->
Copy link
Member

Choose a reason for hiding this comment

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

It should be in the BOM.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

@loicmathieu
Copy link
Contributor Author

loicmathieu commented Jul 18, 2019

@gsmet, @machi1990 thanks for the hard work on reviewing the doc and the implementation details :)
I correct a lot of your feedback and let some questions ... can you close what is OK for you ?

@loicmathieu
Copy link
Contributor Author

@gsmet @machi1990 do you have more feedback? can you close what's OK ?

@machi1990
Copy link
Member

@gsmet @machi1990 do you have more feedback? can you close what's OK ?

hey @loicmathieu feel free to close my comments. I cannot seem to find the Resolve button.

@loicmathieu loicmathieu force-pushed the feat/oauth_provider branch 3 times, most recently from 9f33246 to 3601380 Compare July 25, 2019 16:09
@loicmathieu
Copy link
Contributor Author

@gsmet I rebase on master to resolves the conflict and squash some commits

@gsmet
Copy link
Member

gsmet commented Jul 26, 2019

@loicmathieu so on the documentation side, the last thing to fix is https://github.com/quarkusio/quarkus/pull/3078/files#r304518743 . Please include your proposal + my fix to "OAuth2".

As for the extension itself, it seems @sberyozkin thinks it would be a good addition to the security layer so I suppose we should merge it. I haven't reviewed the code though.

Note: please squash everything into one commit, better to have one atomic commit for the feature.

@loicmathieu loicmathieu force-pushed the feat/oauth_provider branch from 3601380 to b958ca1 Compare July 29, 2019 08:03
@loicmathieu
Copy link
Contributor Author

@gsmet I update the documentation as requested and squashed everything in a single commit.

@gsmet gsmet added this to the 0.20.0 milestone Jul 30, 2019
Copy link
Member

@gsmet gsmet left a comment

Choose a reason for hiding this comment

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

Approved on behalf of the Security team.

@gsmet gsmet merged commit 8963cd7 into quarkusio:master Jul 30, 2019
@loicmathieu loicmathieu deleted the feat/oauth_provider branch July 30, 2019 14:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants