micronaut-projects / micronaut-security

The official Micronaut security solution
Apache License 2.0
169 stars 126 forks source link

JWT Cookies only work if micronaut.security.authentication is set to cookie #419

Open Panthaaaa opened 4 years ago

Panthaaaa commented 4 years ago

io.micronaut.security.token.jwt.cookie.JwtCookieConfigurationProperties requires io.micronaut.security.authentication.CookieBasedAuthenticationModeCondition which should only be used if login/logout is handled by micronaut. We use header and cookie based auth with externally (keycloak) provided tokens and don't want to enable login/logout in micronaut.

This was added a few weeks ago by #397 at https://github.com/micronaut-projects/micronaut-security/blob/b03a7a64e08eefd216b13c9ccdae7befc71114e9/security-jwt/src/main/java/io/micronaut/security/token/jwt/cookie/JwtCookieConfigurationProperties.java#L38

Task List

Steps to Reproduce

  1. Create REST-Endpoint with @Secured
  2. Use externally created JWT cookie to authenticate
  3. 401 is produced because cookie is not read

Expected Behaviour

JWT with Cookies should work like documented only by setting micronaut.security.token.jwt.cookie settings and not micronaut.security.authentication to cookie.

Actual Behaviour

micronaut.security.authentication needs to be set to cookie to enable reading JWT cookies. Otherwise the request errors with 401 and the following log is written:

14:53:15.823 [default-nioEventLoopGroup-1-2] DEBUG i.m.s.a.AuthenticationModeCondition - CookieBasedAuthenticationModeCondition} is not fulfilled because micronaut.security.authentication is not set. 14:53:15.835 [default-nioEventLoopGroup-1-2] DEBUG i.m.s.t.reader.HttpHeaderTokenReader - Looking for bearer token in Authorization header 14:53:15.835 [default-nioEventLoopGroup-1-2] DEBUG i.m.s.t.reader.DefaultTokenResolver - Request GET, /api/1ee99404-f100-4650-a918-3fb9518c5a80, no token found. 14:53:15.836 [default-nioEventLoopGroup-1-2] DEBUG i.m.security.filters.SecurityFilter - No Authentication fetched for request. GET /api/1ee99404-f100-4650-a918-3fb9518c5a80. 14:53:15.837 [default-nioEventLoopGroup-1-2] DEBUG i.m.s.rules.InterceptUrlMapRule - One or more of the IP patterns matched the host address [127.0.0.1]. Continuing request processing. 14:53:15.838 [default-nioEventLoopGroup-1-2] DEBUG i.m.s.rules.AbstractSecurityRule - None of the given roles [[isAnonymous()]] matched the required roles [[user, admin]]. Rejecting the request 14:53:15.838 [default-nioEventLoopGroup-1-2] DEBUG i.m.security.filters.SecurityFilter - Unauthorized request GET /api/1ee99404-f100-4650-a918-3fb9518c5a80. The rule provider io.micronaut.security.rules.SecuredAnnotationRule rejected the request. 14:53:15.871 [default-nioEventLoopGroup-1-2] INFO HTTP_ACCESS_LOGGER - localhost [20/Oct/2020:14:53:15 +0200] "GET /api/1ee99404-f100-4650-a918-3fb9518c5a80 HTTP/1.1" 401 - "'https://master.dev/'" "'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.80 Safari/537.36'"

Environment Information

micronautVersion=2.1.1 application.yml security config

micronaut:
  security:
    endpoints:
      login:
        enabled: false
      logout:
        enabled: false
      oauth:
        enabled: false
      keys:
        enabled: false
    token:
      name-key: "user_id"
      jwt:
        cookie:
          enabled: true
          cookie-name: Authorization
    reject-not-found: false

Example Application

matt-snider commented 3 years ago

Just chiming in with another use case relating to refresh cookies, which also use the same Condition in several spots.

https://github.com/micronaut-projects/micronaut-security/blob/e1ec5ab0e46ce2972cc05b95d47468c663a81da3/security-jwt/src/main/java/io/micronaut/security/token/jwt/cookie/JwtCookieClearerLogoutHandler.java#L39

https://github.com/micronaut-projects/micronaut-security/blob/062135aabd228d5f3a30b5b6a855375e30d5f76e/security-jwt/src/main/java/io/micronaut/security/token/jwt/cookie/RefreshTokenCookieConfigurationProperties.java#L38

I am experimenting with a combination of Bearer and Cookies in an SPA as outlined in this article as well as some of the other articles referenced there (e.g. hasura.io). Briefly, the idea is to keep the access token in memory, and persist the refresh token in an HttpOnly path-specific Cookie so that it will be automatically sent on refresh token requests. This avoids saving it in local storage.

I implemented this by extending the AccessRefreshTokenLoginHandler and implementing my own LogoutHandler to set and remove the cookie. This seems to work fine, but I had thought it might work OOB by just specifying the refresh token cookie configuration, and then after I realized it didn't, I thought I could reuse the existing RefreshTokenCookieConfiguration. If the condition were changed, this might work without additional changes in logout (e.g. JwtCookieClearerLogoutHandler), but I think the AccessRefreshTokenLoginHandler would need to be modified to actually set the cookie. I'm wondering if this is something that makes sense for the project or whether this use case is too specialized.

jameskleeh commented 3 years ago

@Panthaaaa If you are using external tokens then the value should be micronaut.security.authentication: idtoken, which will enable that bean the same as cookie

jameskleeh commented 3 years ago

@matt-snider I think I understand what you want to achieve, but that isn't related to this issue I don't think. You can file a new issue and explain in detail what we could do to improve this library