GithubHelp home page GithubHelp logo

polyvariant / sttp-oauth2 Goto Github PK

View Code? Open in Web Editor NEW
73.0 9.0 22.0 1.74 MB

OAuth2 client library implemented in Scala using sttp

Home Page: https://sttp-oauth2.polyvariant.org

License: Apache License 2.0

Scala 93.20% JavaScript 5.70% CSS 1.10%
scala sttp oauth2 cats-effect

sttp-oauth2's People

Contributors

blondacz avatar bwiercinski avatar github-actions[bot] avatar jwojnowski avatar kubukoz avatar majk-p avatar matwojcik avatar mergify[bot] avatar pavulonx avatar sbrunk avatar scala-steward avatar tplaskowski avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

sttp-oauth2's Issues

Cats Effect 3 support

Hi, the lib is lacking of CE3 support. There is an open PR #64, but I it is outdated. I will create new PR.

Based on current module name "sttp-oauth2-cache-ce2" I suspect that the place of the CE3 support is in the separate module. I'm proposing the name "sttp-oauth2-cache-cats" since in this module there will be always current version of cats.

Decoding broken for missing fields

With #18 introduced, we are now unable to decode Oauth2 responses with fields missing in UserInfo as Decoder.forProduct wouldn't know the default value in case class.

Missing DPoP support

Private-key signing of access tokens support would be nice, to support DPoP.

Although still in draft, there's already a Java based implementation in Nimbus, which should be license-compatible.

Client credentials backend (interceptor) module

I would like to add a new module with backend implementation, that is:

  • taking another SttpBackend as an input
  • gets a new token with given scope
  • adds header "Bearer"
  • saves the token in some Ref
  • checks if the token is not expired and reuses it

After consultations with @majk-p I believe that such backend only makes sense in the case of the ClientCredentials functionality (i.e. app2app communication https://tools.ietf.org/html/rfc6749#section-4.4)

That's why I suggest following names:

  1. sttp-oauth2-app-to-app-backend, class name: SttpOauth2AppToAppBackend
  2. sttp-oauth2-client-credentials-backend, class name: SttpOauth2ClientCredentialsBackend
  3. sttp-oauth2-backend, class name: SttpOauth2Backend
  4. sttp-oauth2-app-to-app, class name: SttpOauth2AppToApp
  5. other?

(alternatively instead of sttp-oauth2-app-to-app prefix we can use sttp-oauth2-app2app, but it looks strange with doubled "2")

other topic:

I need some engine to store currently valid token. one option is to use cats effect's Ref, but many of our clients may not want to have CE in their classpaths (just like sttp::core). Since this logic is very simple we can use AtomicReference for that. WDYT?

Client credentials backend may use expired token, because of network latency

possible bug:
if this check (isBefore) is evaluated just before token expires, then because of duration of request dispatching and network latency, when the request will come to the requested server it may be already expired.

i see 2 options:

  • retry whole process once when 401 will be responded
  • add threshold (let say default 10 seconds). then use (expiryInstant - threshold)

Originally posted by @bwiercinski in #46 (comment)

AccessTokenResponse contains non-standard attribute

I'm running into a decoding failure when attempting a client credentials grant because my identity service is not returning a value for domain that AccessTokenResponse expects: https://github.com/ocadotechnology/sttp-oauth2/blob/b605d0be2f943e66a4c38931a3ba71c9e39c966c/oauth2/src/main/scala/com/ocadotechnology/sttp/oauth2/ClientCredentialsToken.scala#L24-L29

According to the OAuth 2.0 spec the only valid attributes for the response are access_token, token_type, expires_in, refresh_token, and scope.

Can domain be made optional, or have I missed something here? I'm using 0.10.1 of your library.

Add support for omitting scope

Hey all, it'd be nice if there existed an ability to omit the scope, since scope is optional. Based on the spec it looks like the scope cannot be empty if a value is being passed, so introducing Scope.empty or a similar value is probably not the right thing to do here, although that'd be the most expedient solution.

Circe cleanup

In circe object there's a configuration and codec for seconds available. Those should be used in whole library, especially in Oauth2TokenResponse.scala and RefreshTokenResponse.scala.

Caching instance of TokenIntrospection

Create caching instance of TokenIntrospection in similar manner to CachingAccessTokenProvider.

https://www.oauth.com/oauth2-servers/token-introspection-endpoint/

Consumers of the introspection endpoint may wish to cache the response of the endpoint for performance reasons. As such, it is important to consider the performance and security trade-offs when deciding to cache the values. For example, shorter cache expiration times will result in higher security since the resource servers will have to query the introspection endpoint more frequently, but will result in an increased load on the endpoint. Longer expiration times leave a window open where a token may actually be expired or revoked, but still be able to be used at a resource server for the remaining duration of the cache time.

One way to mitigate this problem is for consumers to never cache the value beyond the expiration time of the token, which would have been returned in the โ€œexpโ€ parameter of the introspection response.

Things to consider:

  • don't cache longer than exp
  • consider memory consumption
  • consider concurrent access to cache and retrieval of tokens
  • make it cache provider agnostic, provide separate modules with concrete implementations (but it is reasonable to start with one)

Pass sttp backend explicitly

Since some time the convention in sttp is not to pass backends implicitly, and we have that in couple of places.

Would be great to do this after #149

Feature idea - improve reliability of caching token introspections

Right now there is a common TTL for every token introspection caching, e.g. 30 minutes. It makes applications really dependent on availability of Oauth2 server. If this becomes unavailable, application stops accepting any incoming requests until the Oauth2 server becomes available, even though the token may be valid for even couple of hours more!

Possible solutions:

  • set TTL to token expiration time, but in the background process try regularly to check if token is valid
  • if the token TTL is about to expire, and application request to validate it, try to call server to validate it, but if this fails with 500 trust the token introspection from cache

Decoder[TokenIntrospectionResponse] - decoding failure on exp field

According to https://tools.ietf.org/html/rfc7662#section-2.2

exp
OPTIONAL. Integer timestamp, measured in the number of seconds
since January 1 1970 UTC, indicating when this token will expire,
as defined in JWT [RFC7519].

Decoder[TokenIntrospectionResponse] uses default Instant decoder which uses ISO_INSTANT DateTimeFormatter.

test that fails:

class IntrospectionSerializationSpec extends AnyWordSpec with Matchers with OptionValues {
  "Token" should {
    "deserialize token introspection response" in {
      val clientId = "Client ID"
      val domain = "zoo"
      val exp = Instant.EPOCH
      val active = false
      val authorities = List("aaa", "bbb")
      val scope = "cfc.first-app_scope"
      val tokenType = "Bearer"

      val json = json"""{
            "client_id": $clientId,
            "domain": $domain,
            "exp": ${exp.getEpochSecond},
            "active": $active,
            "authorities": $authorities,
            "scope": $scope,
            "token_type": $tokenType
          }"""

      json.as[TokenIntrospectionResponse] shouldBe Right(
        TokenIntrospectionResponse(clientId, domain, exp, active, authorities, Scope.of(scope).value, tokenType)
      )

    }
  }
}

Optional scope not working as expected

Hi there!

I'm reaching out because I've been experimenting with the sttp-oauth2 library for the Client Credentials flow, and initially, it seemed like the perfect fit for my project. However, I've encountered a small hiccup that I could really use your help with.

It appears that when attempting to authenticate against my OAuth provider, which doesn't require a specific scope, I'm running into an error. Despite following the documentation and passing val scope: Option[Scope] = Option.empty, I'm consistently met with the following error message: Predicate failed: "" matches ValidScope.

I'm curious if there might be a bug lurking or if there's something crucial I'm overlooking in my implementation. As a temporary workaround to get things moving, I took the liberty of modifying the scopeRegex to private val scopeRegex = """^((\x21|[\x23-\x5b]|[\x5d-\x7e])+(\s(\x21|[\x23-\x5b]|[\x5d-\x7e])+)*$)?""".

Any guidance or suggestions you could offer would be immensely appreciated! ๐Ÿ™

ZIO support

Adding a module with native ZIO 2 integration would be cool, are you interested in any contributions?

Validation failure of space-delimited string of Scopes

I'm running into the following "Predicate failed" error, while attempting to validate / decode a Scope of "email profile" included in the client credentials grant access token returned from my identity service. I'm also getting the same error when attempting to define the same scope when instantiating the SttpOauth2ClientCredentialsFutureBackend

image

I believe the error is being thrown from here:

https://github.com/ocadotechnology/sttp-oauth2/blob/63fe2b0d0a0328a7bfd884a8dff1eb7d7186e438/oauth2/src/main/scala/com/ocadotechnology/sttp/oauth2/common.scala#L25-L30

According to the OAuth 2.0 spec a space-delimited, case-sensitive string is a valid value.

Is there possibly an issue with the validation?

I'm using 0.11 of your library. Thanks.

Document `baseUrl`

The baseUrl parameter isn't well documented in the code. It needs some scaladoc.

Transfer to Polyvariant

TL;DR the sttp-oauth2 project will be transferred to https://github.com/polyvariant/

This issue is a followup to the discussion in sttp softwaremill/sttp#2110. It captures the steps we need to take to transfer the project.

Transfer plan

Once the transfer is done we'll proceed with the waiting PRs and issues.

Open questions

For repackaging, should we use org.polyvariant.sttp.oauth2 or just sttp.oauth2?

Support custom login/token URLs

e.g. Spotify uses an /authorize path instead of /login.

Current workaround:

val uri = AuthorizationCodeProvider[Uri, F].loginLink(scope = scopes).path("authorize")

A similar situation happens for the /token path - in Spotify it's /api/token.

Scala 3

Are there any plans to support scala 3?

Semaphore usage in SttpOauth2ClientCredentialsCatsBackend

I'd like to open a discussion about the usage of Semaphore in SttpOauth2ClientCredentialsCatsBackend.

The aim of this was to prevent parallel calls for token e.g. at application startup or when the token expired. When there is nothing in cache and multiple threads are calling this, without a Semaphore all of them would unnecessarily do a http call for token in paralel.

However token life is usually quite long, let's say half an hour. Then all calls during this half an hour are blocked from paralel access to read only resource in memory. This way for a sake of app startup and then one moment every half an hour, we pay a cost for half an hour of obtaining valid token from cache.

I'm not 100% convinced this is a right choice. Of course current cache implementation is in memory and is quite quick, so we don't block for long, but if someone used some more sophisticated caching mechanism, they would unnecessarily pay a cost there.

Some middle-way solution would be creating Semaphore with n e.g. 4 - that would prevent from flooding token service in worst cases, but would enable parallel access in regular situations.

What would be a good thing I guess is doing some performance test, that would measure what is better performance-wise for an application.

Idea: Split ClientCredentialsProvider

ClientCredentialsProvider provides 2 functionalities:

  • retrieves AccessToken
  • validates if AccessToken is valid

In regular scenario you would never use both those functionalities in the same place - first is used in client calls and the second on server side.

Having that in single trait leads to e.g. unnecessarily passing tokenIntrospectionUrl or implementing that in tests that don't need token introspection.

I'd think about splitting that trait into 2 separate.

PasswordGrant fails to decode user_name

Exception in thread "main" com.ocadotechnology.sttp.oauth2.common$OAuth2Exception: Client call resulted in error (200): Missing required field: DownField(user_name)
	at com.ocadotechnology.sttp.oauth2.PasswordGrantProvider$.$anonfun$apply$5(PasswordGrantProvider.scala:30)
	at cats.syntax.EitherOps$.leftMap$extension(either.scala:193)
	at com.ocadotechnology.sttp.oauth2.PasswordGrantProvider$.$anonfun$apply$4(PasswordGrantProvider.scala:30)
	at sttp.client3.monad.IdMonad$.map(IdMonad.scala:8)
	at sttp.monad.syntax$MonadErrorOps.map(MonadError.scala:60)
	at com.ocadotechnology.sttp.oauth2.PasswordGrantProvider$.$anonfun$apply$2(PasswordGrantProvider.scala:30)
	at sttp.monad.syntax$MonadErrorOps.flatMap(MonadError.scala:61)
	at com.ocadotechnology.sttp.oauth2.PasswordGrantProvider$.$anonfun$apply$1(PasswordGrantProvider.scala:31)
	at OauthTest$.main(OauthTest.scala:18)
	at OauthTest.main(OauthTest.scala)
Caused by: com.ocadotechnology.sttp.oauth2.common$Error$HttpClientError: Client call resulted in error (200): Missing required field: DownField(user_name)
	at com.ocadotechnology.sttp.oauth2.common$.$anonfun$responseWithCommonError$1(common.scala:103)
	at sttp.client3.MappedResponseAs.$anonfun$mapWithMetadata$1(ResponseAs.scala:91)
	at sttp.client3.internal.BodyFromResponseAs.$anonfun$doApply$2(BodyFromResponseAs.scala:24)
	at sttp.client3.monad.IdMonad$.map(IdMonad.scala:8)
	at sttp.monad.syntax$MonadErrorOps.map(MonadError.scala:60)
	at sttp.client3.internal.BodyFromResponseAs.doApply(BodyFromResponseAs.scala:24)
	at sttp.client3.internal.BodyFromResponseAs.$anonfun$apply$1(BodyFromResponseAs.scala:14)
	at sttp.monad.syntax$MonadErrorOps.map(MonadError.scala:60)
	at sttp.client3.internal.BodyFromResponseAs.apply(BodyFromResponseAs.scala:14)
	at sttp.client3.HttpURLConnectionBackend.readResponse(HttpURLConnectionBackend.scala:245)
	at sttp.client3.HttpURLConnectionBackend.$anonfun$send$1(HttpURLConnectionBackend.scala:56)
	at scala.util.Try$.apply(Try.scala:209)
	at sttp.monad.MonadError.handleError(MonadError.scala:24)
	at sttp.monad.MonadError.handleError$(MonadError.scala:23)
	at sttp.client3.monad.IdMonad$.handleError(IdMonad.scala:6)
	at sttp.client3.SttpClientException$.adjustExceptions(SttpClientException.scala:58)
	at sttp.client3.HttpURLConnectionBackend.adjustExceptions(HttpURLConnectionBackend.scala:295)
	at sttp.client3.HttpURLConnectionBackend.send(HttpURLConnectionBackend.scala:30)
	at sttp.client3.HttpURLConnectionBackend.send(HttpURLConnectionBackend.scala:22)
	at sttp.client3.FollowRedirectsBackend.sendWithCounter(FollowRedirectsBackend.scala:29)
	at sttp.client3.FollowRedirectsBackend.send(FollowRedirectsBackend.scala:24)
	at com.ocadotechnology.sttp.oauth2.PasswordGrant$.$anonfun$requestToken$1(PasswordGrant.scala:40)
	at sttp.monad.syntax$MonadErrorOps.map(MonadError.scala:60)
	at com.ocadotechnology.sttp.oauth2.PasswordGrant$.requestToken(PasswordGrant.scala:42)
	at com.ocadotechnology.sttp.oauth2.PasswordGrantProvider$.$anonfun$apply$3(PasswordGrantProvider.scala:29)
	... 6 more
Caused by: sttp.client3.DeserializationException: Missing required field: DownField(user_name)
	at sttp.client3.ResponseAs$.$anonfun$deserializeWithError$1(ResponseAs.scala:201)
	at sttp.client3.ResponseAs$.$anonfun$deserializeRightWithError$1(ResponseAs.scala:180)
	at sttp.client3.MappedResponseAs.$anonfun$mapWithMetadata$1(ResponseAs.scala:91)
	... 30 more

Originally posted by @reoxu in #335 (comment)

Use apply instead of instance

In couple of places there is def instance used on companion objects to create instance, once in sttp a convention is to use apply

Feature idea - improve reliability of caching the tokens

Right now tokens are cached until its expiration time (which is also a problem - see #48). If during the time it gets expired, OAuth2 server is unavailable, then application is completely blocked, because it cannot obtain new token.

Possible solution - background process, which tries to obtain new token in some configured period. E.g. if the token expiration is in 12h, then first try to get new token 4 hours before expiration, if that fails then in 2 hours from expiration, then 1, 0:30, 0:15, 0:10 etc.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.