polyvariant / sttp-oauth2 Goto Github PK
View Code? Open in Web Editor NEWOAuth2 client library implemented in Scala using sttp
Home Page: https://sttp-oauth2.polyvariant.org
License: Apache License 2.0
OAuth2 client library implemented in Scala using sttp
Home Page: https://sttp-oauth2.polyvariant.org
License: Apache License 2.0
Have you considered using https://github.com/plokhotnyuk/jsoniter-scala for JSON (de)serialization?
Just wanted to try out this library with the new json-variant, but I could not find version 0.17.0
on maven. It seems the CI failed: https://github.com/ocadotechnology/sttp-oauth2/actions/runs/6770976275
Is it possible maybe fix this with a retry?
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
I believe the error is being thrown from here:
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.
I would like to add a new module with backend implementation, that is:
Ref
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:
sttp-oauth2-app-to-app-backend
, class name: SttpOauth2AppToAppBackendsttp-oauth2-client-credentials-backend
, class name: SttpOauth2ClientCredentialsBackendsttp-oauth2-backend
, class name: SttpOauth2Backendsttp-oauth2-app-to-app
, class name: SttpOauth2AppToApp(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?
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.
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
.
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)
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.
Once the transfer is done we'll proceed with the waiting PRs and issues.
For repackaging, should we use org.polyvariant.sttp.oauth2
or just sttp.oauth2
?
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.
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.
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.
Adding a module with native ZIO 2 integration would be cool, are you interested in any contributions?
The Spotify API only returns:
Reason: olafurpg/setup-scala#49
Similarly to https://github.com/ocadotechnology/pass4s we could go with sbt-typelevel
Are there any plans to support scala 3?
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
Cache modules introduced in #295 currently don't support Scala.js due to the lack of realTimeInstant
on js platform.
We should port those modules to provide Scala.js support.
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
.
In couple of places there is def instance
used on companion objects to create instance, once in sttp a convention is to use apply
The baseUrl
parameter isn't well documented in the code. It needs some scaladoc.
ClientCredentialsProvider
provides 2 functionalities:
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.
To limit the number of dependencies, circe-generic
and circe-generic-extras
could be dropped. This requires replacing currently derived circe decoders with hand-written ones.
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:
Originally posted by @bwiercinski in #46 (comment)
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:
exp
Note that you can use TestContext()
to do a sleep without actually waiting in the tests. I would recommend to do that, but it's not a blocker for now (with just a couple tests). See https://typelevel.org/cats-effect/testing/#testing-concurrency for some info
Originally posted by @kubukoz in #46 (comment)
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! ๐
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)
)
}
}
}
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.
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.
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:
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.