Skip to content

Add Auth0 support#547

Merged
arvindkrishnakumar-okta merged 4 commits intookta:masterfrom
deepu105:auth0-support
Mar 7, 2023
Merged

Add Auth0 support#547
arvindkrishnakumar-okta merged 4 commits intookta:masterfrom
deepu105:auth0-support

Conversation

@deepu105
Copy link
Copy Markdown
Contributor

@deepu105 deepu105 commented Feb 21, 2023

Initial support for Auth0 hosted login fix #358

@deepu105 deepu105 marked this pull request as ready for review February 21, 2023 09:50
@deepu105
Copy link
Copy Markdown
Contributor Author

@arvindkrishnakumar-okta @mraible initial support for auth0 hosted login. I also updated the implementation to lookup the well-known metadata instead of hard coded values (with hard coded values as backup)

@arvindkrishnakumar-okta can you please review? I left some TODO comments in code, would appreciate your input on it

@mraible
Copy link
Copy Markdown
Contributor

mraible commented Feb 24, 2023

I tried checking out this branch and using it with okta-samples/okta-spring-boot-sample. Steps I used:

  1. git clone [email protected]:okta-samples/okta-spring-boot-sample.git
  2. auth0 apps create: Regular Web Application, callback URL of http://localhost:8080/login/oauth2/code/auth0, logout URL of http://localhost:8080
  3. Modified .okta.env to have my Auth0 app's values.
  4. Modified pom.xml to use version 3.0.3-SNAPSHOT for the Okta Spring Boot starter.
  5. Started the app with ./mvnw spring-boot:run.

I received an error on startup:

08:00:21.791 [restartedMain] ERROR org.springframework.boot.SpringApplication - Application run failed
java.lang.IllegalArgumentException: Could not resolve placeholder 'env.ISSUER' in value "${env.ISSUER}"

This seems to be related to spring-dotenv, so I removed it and put the values directly in application.properties.

okta.oauth2.issuer=https://dev-06bzs1cu.us.auth0.com/
okta.oauth2.client-id=w12NPf1LIgCgM...
okta.oauth2.client-secret=...V0Rus8KUfN

I started again, opened my browser to http://localhost:8080, and received an error when I clicked the login button.

unauthorized_client: Callback URL mismatch. http://localhost:8080/login/oauth2/code/okta is not in the list of allowed callback URLs

I added this URL to my Auth0 app's callback URLs and refreshed my browser. This time, it worked!

I'm able to see all my claims on the profile page.

Screenshot 2023-02-24 at 8 04 50 AM

One question: is the aud claim supposed to match the client ID?

I noticed the logout doesn't work. This is probably because Auth0 doesn't have an end_session_endpoint like Okta does.

I also tried to access the Spring Boot app as a resource server. I created an access token using the Auth0 CLI.

auth0 test token -a https://dev-06bzs1cu.us.auth0.com/api/v2/

I set the access token as a TOKEN variable:

TOKEN=...

Then, used HTTPie to try and access the /hello endpoint.

http :8080/hello "Authorization: Bearer $TOKEN"

This results in an error:

2023-02-24T08:09:28.554-07:00 ERROR 4859 --- [nio-8080-exec-6] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception

org.springframework.security.authentication.AuthenticationServiceException: 404 Not Found: "Not found."
        at org.springframework.security.oauth2.server.resource.authentication.OpaqueTokenAuthenticationProvider.getOAuth2AuthenticatedPrincipal(OpaqueTokenAuthenticationProvider.java:132) ~[spring-security-oauth2-resource-server-6.0.2.jar:6.0.2]

Since Auth0 only returns a JWT when you specify a valid audience, I added my audience to application.properties:

okta.oauth2.audience=https://dev-06bzs1cu.us.auth0.com/api/v2/

I restarted and tried again. I get the same error.

@deepu105
Copy link
Copy Markdown
Contributor Author

deepu105 commented Feb 27, 2023

I updated the impl to use opaque tokens only for Okta domains. Now for AS I'm able to get past the issue @mraible had since auth0 domains now use JWT mode. But now there is an NPE from spring security, probably some issue within the UserUtil, I'll do some debugging, in the meantime @arvindkrishnakumar-okta any pointers? Here is the trace

Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: java.lang.NullPointerException: Cannot invoke "org.springframework.security.oauth2.core.oidc.user.OidcUser.getFullName()" because "oidcUser" is null] with root cause

java.lang.NullPointerException: Cannot invoke "org.springframework.security.oauth2.core.oidc.user.OidcUser.getFullName()" because "oidcUser" is null
        at com.example.sample.Application$ExampleRestController.sayHello(Application.java:47) ~[classes/:na]
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
        at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
        at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:207) ~[spring-web-6.0.5.jar:6.0.5]
        at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:152) ~[spring-web-6.0.5.jar:6.0.5]
        at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117) ~[spring-webmvc-6.0.5.jar:6.0.5]
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:884) ~[spring-webmvc-6.0.5.jar:6.0.5]
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:797) ~[spring-webmvc-6.0.5.jar:6.0.5]
        at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-6.0.5.jar:6.0.5]
        at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1081) ~[spring-webmvc-6.0.5.jar:6.0.5]
        at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:974) ~[spring-webmvc-6.0.5.jar:6.0.5]
        at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1011) ~[spring-webmvc-6.0.5.jar:6.0.5]
        at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:903) ~[spring-webmvc-6.0.5.jar:6.0.5]
        at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:705) ~[tomcat-embed-core-10.1.5.jar:6.0]
        at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) ~[spring-webmvc-6.0.5.jar:6.0.5]
        at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:814) ~[tomcat-embed-core-10.1.5.jar:6.0]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:223) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
        at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-10.1.5.jar:10.1.5]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
        at org.springframework.security.web.FilterChainProxy.lambda$doFilterInternal$3(FilterChainProxy.java:231) ~[spring-security-web-6.0.2.jar:6.0.2]
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:365) ~[spring-security-web-6.0.2.jar:6.0.2]
        at org.springframework.security.web.access.intercept.AuthorizationFilter.doFilter(AuthorizationFilter.java:100) ~[spring-security-web-6.0.2.jar:6.0.2]
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.0.2.jar:6.0.2]
        at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:126) ~[spring-security-web-6.0.2.jar:6.0.2]
        at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:120) ~[spring-security-web-6.0.2.jar:6.0.2]
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.0.2.jar:6.0.2]
        at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:100) ~[spring-security-web-6.0.2.jar:6.0.2]
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.0.2.jar:6.0.2]
        at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:179) ~[spring-security-web-6.0.2.jar:6.0.2]
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.0.2.jar:6.0.2]
        at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63) ~[spring-security-web-6.0.2.jar:6.0.2]
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.0.2.jar:6.0.2]
        at org.springframework.security.oauth2.server.resource.web.authentication.BearerTokenAuthenticationFilter.doFilterInternal(BearerTokenAuthenticationFilter.java:145) ~[spring-security-oauth2-resource-server-6.0.2.jar:6.0.2]
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.0.5.jar:6.0.5]
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.0.2.jar:6.0.2]
        at org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter.doFilterInternal(DefaultLogoutPageGeneratingFilter.java:58) ~[spring-security-web-6.0.2.jar:6.0.2]
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.0.5.jar:6.0.5]
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.0.2.jar:6.0.2]
        at org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter.doFilter(DefaultLoginPageGeneratingFilter.java:188) ~[spring-security-web-6.0.2.jar:6.0.2]
        at org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter.doFilter(DefaultLoginPageGeneratingFilter.java:174) ~[spring-security-web-6.0.2.jar:6.0.2]
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.0.2.jar:6.0.2]
        at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:227) ~[spring-security-web-6.0.2.jar:6.0.2]
        at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:221) ~[spring-security-web-6.0.2.jar:6.0.2]
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.0.2.jar:6.0.2]
        at org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter.doFilterInternal(OAuth2AuthorizationRequestRedirectFilter.java:181) ~[spring-security-oauth2-client-6.0.2.jar:6.0.2]
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.0.5.jar:6.0.5]
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.0.2.jar:6.0.2]
        at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:107) ~[spring-security-web-6.0.2.jar:6.0.2]
        at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:93) ~[spring-security-web-6.0.2.jar:6.0.2]
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.0.2.jar:6.0.2]
        at org.springframework.security.web.csrf.CsrfFilter.doFilterInternal(CsrfFilter.java:117) ~[spring-security-web-6.0.2.jar:6.0.2]
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.0.5.jar:6.0.5]
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.0.2.jar:6.0.2]
        at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90) ~[spring-security-web-6.0.2.jar:6.0.2]
        at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75) ~[spring-security-web-6.0.2.jar:6.0.2]
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.0.5.jar:6.0.5]
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.0.2.jar:6.0.2]
        at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:82) ~[spring-security-web-6.0.2.jar:6.0.2]
        at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:69) ~[spring-security-web-6.0.2.jar:6.0.2]
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.0.2.jar:6.0.2]
        at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:62) ~[spring-security-web-6.0.2.jar:6.0.2]
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.0.5.jar:6.0.5]
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.0.2.jar:6.0.2]
        at org.springframework.security.web.session.DisableEncodeUrlFilter.doFilterInternal(DisableEncodeUrlFilter.java:42) ~[spring-security-web-6.0.2.jar:6.0.2]
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.0.5.jar:6.0.5]
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.0.2.jar:6.0.2]
        at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:233) ~[spring-security-web-6.0.2.jar:6.0.2]
        at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:191) ~[spring-security-web-6.0.2.jar:6.0.2]
        at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:352) ~[spring-web-6.0.5.jar:6.0.5]
        at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:268) ~[spring-web-6.0.5.jar:6.0.5]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
        at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-6.0.5.jar:6.0.5]
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.0.5.jar:6.0.5]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
        at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-6.0.5.jar:6.0.5]
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.0.5.jar:6.0.5]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
        at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-6.0.5.jar:6.0.5]
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.0.5.jar:6.0.5]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
        at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:177) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
        at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
        at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:119) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
        at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:400) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
        at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
        at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:859) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
        at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1734) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
        at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
        at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
        at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
        at java.base/java.lang.Thread.run(Thread.java:833) ~[na:an]

@arvindkrishnakumar-okta
Copy link
Copy Markdown
Contributor

@deepu105 I'm guessing, the OidcUser info returned in Auth0's OIDC metadata doesn't contain/map to the necessary OIDC user info required by Spring Sec's internal mapping.

@mraible
Copy link
Copy Markdown
Contributor

mraible commented Feb 27, 2023

@deepu105 I believe this is expected and there might be a bug in the okta-spring-boot-sample. The error comes from this line. When you log in with Spring Security's oauth2Login(), you can use @AuthenticationPrincipal OidcUser oidcUser in a method signature and it will work. However, if you're accessing the method with a JWT (and the Spring Boot app is acting as a resource server), this will not resolve. You have to use Principal in the arguments or @AuthenticationPrincipal Jwt jwt.

If I try http :8080/hello "Authorization: Bearer $TOKEN on the okta-spring-boot-sample with Okta, it fails too:

{
    "error": "Internal Server Error",
    "message": "Cannot invoke \"org.springframework.security.oauth2.core.oidc.user.OidcUser.getFullName()\" because \"oidcUser\" is null",
    "path": "/hello",
    ...
}

I fixed the sample in okta-samples/okta-spring-boot-sample@3311fb6.

@deepu105
Copy link
Copy Markdown
Contributor Author

deepu105 commented Feb 27, 2023 via email

@deepu105
Copy link
Copy Markdown
Contributor Author

@mraible it works with your fix on the sample app

issuer += "/";
}
ResponseEntity<String> response
= restTemplate.getForEntity(issuer + ".well-known/openid-configuration", String.class);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Does this lookup happen on startup? When I was working on my native Java examples post, I discovered that Spring Security did a metadata lookup on startup. I convinced them they should do it on the first request instead, so the startup time would be faster. It'd be unfortunate if this reverted the default Spring Security behavior.

The best way to test this is likely to create two different Spring Boot apps. One uses the Okta Spring Boot starter and one uses raw Spring Security. Compile them both to native images. Then compare startup times.

Copy link
Copy Markdown
Contributor

@arvindkrishnakumar-okta arvindkrishnakumar-okta left a comment

Choose a reason for hiding this comment

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

LGTM

@deepu105
Copy link
Copy Markdown
Contributor Author

deepu105 commented Mar 1, 2023

@arvindkrishnakumar-okta do you think we can merge this and make a release. I can then write a blog post showing how to use this with auth0

@mraible
Copy link
Copy Markdown
Contributor

mraible commented Mar 1, 2023

I just tried this again and can confirm that both login and access the /hello endpoint works with the okta-spring-boot-sample.

I also remembered this sample has a webflux branch. I figured it'd be a good idea to test that one too, so I upgraded it to Spring Boot 3 in okta-samples/okta-spring-boot-sample#71.

Unfortunately, when I configure this sample for Okta with okta start, the redirect URI is invalid. The app sends http://%5B0:0:0:0:0:0:0:1%5D:8080/login/oauth2/code/okta rather than http://localhost:8080/login/oauth2/code/okta.

To see if this happens with Spring MVC, I tried the sample's main branch with okta start and 3.0.3-SNAPSHOT. I get the following error:

11:07:47.270 [restartedMain] ERROR org.springframework.boot.SpringApplication - Application run failed
java.lang.IllegalArgumentException: Could not resolve placeholder 'env.ISSUER' in value "${env.ISSUER}"

This seems to indicate me.paulschwarz:spring-dotenv:2.5.4 doesn't work with Spring Boot 3. I fixed it in okta-samples/okta-spring-boot-sample#72.

It seems that Okta works fine now. However, there's an issue with WebFlux.

@deepu105
Copy link
Copy Markdown
Contributor Author

deepu105 commented Mar 2, 2023

@mraible do you think the webflux issue is a blocker to merge this PR?

@mraible
Copy link
Copy Markdown
Contributor

mraible commented Mar 2, 2023

@deepu105 No. I tested it w/o this PR and it still happens. I created #554 to track. I don't think we should release a new version until this is fixed.

@jimmyjames
Copy link
Copy Markdown
Contributor

I've done some testing of this PR, nice work! Using the okta-spring-boot-sample I also verified that login/logout with a Servlet application works (from comments looks like there is an issue with WebFlux login unrelated to this PR).

I also tested this change with a resource server app. I was able to secure endpoints including only allowing access based on scopes present in the JWT.

One thing that isn't accounted for here is being able to use the permissions claim to make authorization decisions. This is part of the Role-Based Access Control feature (RBAC). I tried initializing my own jwtGrantedAuthoritiesConverter bean but it conflicts with the OktaJwtAuthenticationConverter in the starter, which extracts authorities from the groups claim. It's possible to overwrite this bean by setting spring.main.allow-bean-definition-overriding=true, but that's not intuitive and could have other consequences.

I don't think it needs to be part of this PR, but at some point this starter should extract authorities from the permissions claim for Auth0 RBAC use-cases.

@deepu105
Copy link
Copy Markdown
Contributor Author

deepu105 commented Mar 4, 2023

Nice findings @jimmyjames can you open an issue to track the permission issue

@jimmyjames
Copy link
Copy Markdown
Contributor

@mraible, this is interesting, I'm seeing the same thing when trying to update the Auth0 WebFlux sample for Spring Boot 3. Since that sample does not use this starter (just uses Spring directly), seems something might be wrong with WebFlux itself in this scenario, or we're missing some required changes.

Unfortunately, when I configure this sample for Okta with okta start, the redirect URI is invalid. The app sends http://%5B0:0:0:0:0:0:0:1%5D:8080/login/oauth2/code/okta rather than http://localhost:8080/login/oauth2/code/okta.

@mraible
Copy link
Copy Markdown
Contributor

mraible commented Mar 6, 2023

@jimmyjames That's good to know. Can you please enter an issue in Spring Security's issue tracker? https://github.com/spring-projects/spring-security/issues

If you don't have time, let me know and I can do it. They typically like a reproducible example.

@jimmyjames
Copy link
Copy Markdown
Contributor

@mraible I think I discovered the issue; looks like redirect URIs with localhost are rejected. The spring WebFlux login sample includes a custom WebFilter to handle this.

/**
 * This filter ensures that the loopback IP <code>127.0.0.1</code> is used to access the
 * application so that the sample works correctly, due to the fact that redirect URIs with
 * "localhost" are rejected by the Spring Authorization Server, because the OAuth 2.1
 * draft specification states:
 *
 * <pre>
 *     While redirect URIs using localhost (i.e.,
 *     "http://localhost:{port}/{path}") function similarly to loopback IP
 *     redirects described in Section 10.3.3, the use of "localhost" is NOT
 *     RECOMMENDED.
 * </pre>

I'll try it out and see if it works and follow-up.

@jimmyjames
Copy link
Copy Markdown
Contributor

I'll try it out and see if it works and follow-up.

It does work, consider something similar for #554

@mraible
Copy link
Copy Markdown
Contributor

mraible commented Mar 7, 2023

@jimmyjames I tried adding that filter to the okta-spring-boot-sample's webflux branch it it doesn't seem to help. The initial redirect still contains redirect_uri=http://%5B0:0:0:0:0:0:0:1%5D:8080/login/oauth2/code/okta, which is rejected because Okta expects http://localhost:8080, not http://%5B0:0:0:0:0:0:0:1%5D:8080. It seems like we need to fix the host resolution somehow.

@mraible
Copy link
Copy Markdown
Contributor

mraible commented Mar 7, 2023

I created a PR to update the webflux branch of the okta-spring-boot-sample today.

okta-samples/okta-spring-boot-sample#75

The localhost issue no longer exists. It seems it was fixed in Spring Boot 3.0.4.

I also tested this PR with Auth0 and everything works but the logout (likely because there's no end_session_endpoint in the OIDC metadata. For comparison, you can see there is one for Okta.

@deepu105
Copy link
Copy Markdown
Contributor Author

deepu105 commented Mar 7, 2023 via email

@mraible
Copy link
Copy Markdown
Contributor

mraible commented Mar 7, 2023

That is correct. Excellent work, @deepu105! 😃

@arvindkrishnakumar-okta arvindkrishnakumar-okta merged commit 96833c1 into okta:master Mar 7, 2023
@deepu105 deepu105 deleted the auth0-support branch March 10, 2023 11:07
@arvindkrishnakumar-okta arvindkrishnakumar-okta changed the title Add auth0 support Add Auth0 support Mar 30, 2023
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.

Fix hard coded endpoints

5 participants