scala-oauth2-provider: OAuth 2.0 server side for Scala applications
OAuth 2.0 server-side implementation written in Scala
At a glance
- What is it?
- scala-oauth2-provider is a Scala implementation of the server side of OAuth 2.0, covering the authorization code grant with PKCE, resource owner password credentials, client credentials and the implicit grant, with Bearer tokens and a DataHandler trait you implement against your own user model. It is a core library with separate adapters for Play and Akka HTTP, and its newest published release is from 2023.
- Who is it for?
- Adopt scala-oauth2-provider if you are writing an OAuth 2.0 server in Scala and want the protocol mechanics handled while you keep control of your own user and client storage through the DataHandler trait, and you are comfortable wiring it into Play or Akka HTTP.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 39 days ago.
- What is it written in?
- Mainly Scala, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 25, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The server half of OAuth 2.0, and what that means
The repository is described as the OAuth 2.0 server-side implementation written in Scala, and the README links the specification directly, RFC 6749, as the definition of what is being implemented. That is the scope in one sentence, and it is worth being precise about which half of the protocol this is.
An OAuth 2.0 system has two sides. The client side is the application that wants access to a resource, and it is where the user sees a consent screen, a redirect and a token exchange. The server side is the authorization server that authenticates the user, issues the code or the token, and later validates that token at the resource endpoint. This library is the server side.
The consequence is that adopting it does not give you a login page, a consent screen or a client application. It gives you the parts of the protocol that are fiddly and easy to get subtly wrong, and it leaves the parts that are specific to your product to you.
The provenance matters too. The README says the idea of the library originally comes from oauth2-server, a Java implementation of OAuth 2.0 by yoichiro, so this is a Scala port of an existing design rather than a design from scratch, and the shape of the API follows that lineage.
It targets two web frameworks specifically. The README says it provides OAuth 2.0 server-side functionality and supporting functions for Play Framework and Akka HTTP, and the framework integrations live in their own repositories, play2-oauth2-provider and akka-http-oauth2-provider, so the core library is kept separate from the HTTP layer.
That structure is the main architectural decision in the project, and it is a good one. The protocol logic does not depend on the web framework, so the core can be used from any framework, and the framework packages handle the routing and request plumbing.
The audience is therefore Scala teams who have to be an authorization server and would rather not implement the grant flows, the code exchange and the token validation by hand.
All four grant types, and the Bearer token
The README states that this library supports all grant types, and then lists them.
The authorization code grant, with PKCE authorization code grants supported, is the flow a browser client uses. The client is redirected to the authorization endpoint, the user approves, the server returns a code, and the client exchanges the code for a token.
The resource owner password credentials grant is the flow where the client passes the user's username and password directly to the token endpoint. The client credentials grant is the machine-to-machine flow, where the client authenticates as itself and gets a token for its own use. The implicit grant is the older browser flow where the token comes back in the URL fragment rather than through a code exchange.
And there is an access token type called Bearer, referencing RFC 6750, which is the standard way a token is presented to a resource server.
The claim of supporting all grant types is where the interesting engineering judgement sits, because two of those four flows are ones the wider OAuth community has moved away from. The resource owner password credentials grant requires the client to handle the user's password, which is why it was deprecated in OAuth 2.0 Security Best Current Practice, and the implicit grant returns a token in a URL where it can leak through the referrer header and the browser history. The implicit grant in particular is not recommended for new implementations by that same guidance.
A library that supports all four is not endorsing all four. It is giving you the vocabulary, and the choice of which flow to expose is a decision the README leaves entirely to the application, since the grant types are enabled by which handlers you wire in.
That is a defensible position for a library that predates the current guidance, and it also means that a team adopting it today has to make the grant selection decision themselves rather than inheriting a safe default.
DataHandler: the interface where your application lives
Whether you use Play Framework or not, the README says you have to implement the DataHandler trait and make it work with your own User class that may be already defined in your application. That single requirement is the whole integration surface.
The trait is a set of Future returning methods, and the README's example shows the shape:
class MyDataHandler extends DataHandler[User] {
def validateClient(maybeClientCredential: Option[ClientCredential], request: AuthorizationRequest): Future[Boolean] = ???
def findUser(maybeClientCredential: Option[ClientCredential], request: AuthorizationRequest): Future[Option[User]] = ???
def createAccessToken(authInfo: AuthInfo[User]): Future[AccessToken] = ???
def getStoredAccessToken(authInfo: AuthInfo[User]): Future[Option[AccessToken]] = ???
def refreshAccessToken(authInfo: AuthInfo[User], refreshToken: String): Future[AccessToken] = ???
def findAuthInfoByCode(code: String): Future[Option[AuthInfo[User]]] = ???
def findAuthInfoByRefreshToken(refreshToken: String): Future[Option[AuthInfo[User]]] = ???
def deleteAuthCode(code: String): Future[Unit] = ???
def findAccessToken(token: String): Future[Option[AccessToken]] = ???
def findAuthInfoByAccessToken(accessToken: AccessToken): Future[Option[AuthInfo[User]]] = ???
}Read the list and it is the whole lifecycle of an authorization server. There is client validation, there is user lookup, there is issuing a token, there is retrieving a stored token, there is refreshing one, there is the code exchange in both directions, there is the code deletion that makes a code single use, and there is token lookup for resource requests. Nothing about storage is prescribed, so you can back it with a database, a cache, or anything else.
Two details are worth pulling out. Every method returns a Future, so the data access is expected to be asynchronous, and the README addresses the other case directly: if your data access is blocking for the data storage, then you just wrap your implementation in the DataHandler trait with Future.successful. That is an explicit invitation to use it with a blocking store, with the cost that the calling thread waits.
The type parameter is the point of the design. DataHandler[User] is parameterised by your own user type, so the library never has to know what a user is, and the AuthInfo it hands back carries your type through. The README's guidance for more details points at the Scaladoc of DataHandler, which is where the contract of each method actually lives.
AuthInfo, and the PKCE fields that decide your security posture
The DataHandler returns AuthInfo as authorized information, and the README describes it as made up of a set of fields:
case class AuthInfo[User](
user: User,
clientId: Option[String],
scope: Option[String],
redirectUri: Option[String],
codeChallenge: Option[String] = None,
codeChallengeMethod: Option[CodeChallengeMethod] = None
)The first three are the ones every implementation will fill in. The user is the one authorized by the DataHandler. The clientId is the one sent from a client that has been verified by the DataHandler, and the README is precise about a case that trips people up: if your application requires a client id for client authentication, you can get it with val clientId = authInfo.clientId.getOrElse(throw new InvalidClient()). The scope informs the client of the scope of the access token issued. The redirectUri has a hard requirement attached: this value must be enabled on the authorization code grant, which is the redirect URI validation that stops an attacker from using a code against a different client.
The last two fields are the PKCE ones, and the README's comments on them are unusually precise. The codeChallenge is optional, and should only be set if doing a PKCE authorization request, and when set, PKCE rules apply on the authorization code grant handler. The codeChallengeMethod is also optional and is used only by PKCE when a codeChallenge value is also set, and must be either Plain or S256. If it is not specified and codeChallenge is provided, then Plain is assumed, per RFC 7636.
That last sentence is the one to sit with. Plain is the weaker of the two PKCE methods, because the verifier is sent to the token endpoint as-is, which means an attacker who intercepts the authorization request can replay the verifier. S256 hashes the verifier into the challenge, so the challenge alone is not enough to complete the exchange. The library follows the specification's default rather than choosing the safer option, which is correct behaviour for a library and a decision an application has to make deliberately by always setting the method to S256.
The repository carries a .scalafmt.conf at the root, so the code is formatted with scalafmt, and the build is defined by build.sbt with a project directory for the sbt metadata.
Three ways to adopt it, and only two of them are one click
The setup section is short, and it splits the audience into three groups.
If you use Play Framework, the README points at a separate project, play2-oauth2-provider. If you use Akka HTTP, it points at akka-http-oauth2-provider. Both are separate repositories under the same organisation, and both are where the HTTP wiring lives, so the choice of framework determines which adapter you take.
For other frameworks, the instruction is to add the scala-oauth2-core library dependency to your project, and in that case you need to implement your own OAuth provider working with the web framework you use. The dependency is:
libraryDependencies ++= Seq(
"com.nulab-inc" %% "scala-oauth2-core" % "1.6.0"
)The coordinate tells you two things. The double percent sign means the artifact is cross built, so the same line works for Scala 2 and Scala 3, and the group id is com.nulab-inc, which is the Nulab corporate group id rather than a personal one, matching the repository's own owner.
So the trade-off is clear. On Play or Akka HTTP, you take a ready integration and get the endpoints wired. On anything else, you get the protocol core and write the endpoint layer yourself, which is real work but not much of it, since the core already decides what the endpoints must do.
The core artifact is separate from the framework packages on purpose, and that separation is what makes the third path viable at all. A library that bundled the routing into the core would only work on two frameworks, and a library that had no framework support would make the first two paths much harder. Splitting it this way is the difference between a protocol implementation and a product.
One thing the README does not cover is how the two framework packages are versioned against this core, so if you are mixing an adapter with a core version, that compatibility is something to confirm rather than assume.
Release history: Scala 3 in 2023, commits since
The licence is MIT, with the LICENSE file at the root of the repository, so there is no copyleft obligation if you use it in a commercial product.
The release history is short and tells you two different stories depending on which fact you look at.
The releases are 1.6.0 on 2023-10-16, whose title is Support Scala 3, then 1.5.0 on 2019-08-09, titled Support for PKCE, and 1.4.0 on the same day, titled Support Scala 2.13. So PKCE arrived in 1.5.0 and Scala 3 in 1.6.0, and the version line is still 1.x, which tells you the author treats the API as stable enough not to need a major bump for language support.
The commits are more recent. The last push to the repository was on 2026-09-17, so the tree is being worked on even though nothing new has been published to Maven since 2023. That gap is the thing to weigh for a new adopter. A library whose published artifact is three years old but whose repository is being edited is a different proposition from one that is simply dormant, and only one of those is safe to build on without checking.
The practical checks are narrow. Confirm that the Scala version in your build is one the 1.6.0 artifact supports, since the Scala 3 support landed in that release and earlier artifacts are Scala 2 only. Confirm that the Play or Akka HTTP adapter you plan to use is compatible with the core version you pin, since the two are versioned separately. And decide deliberately which grant types you expose, because the library will happily implement the implicit grant and the password credentials grant, and neither is a good default in 2026.
The repository is small and conventional for a Scala project: a src directory, a project directory for the sbt build definition, build.sbt at the root, a .scalafmt.conf, a CI workflow badge in the README pointing at a ci.yml workflow, and the licence file.
Editorial conclusion
Adopt scala-oauth2-provider if you are writing an OAuth 2.0 server in Scala and want the protocol mechanics handled while you keep control of your own user and client storage through the DataHandler trait, and you are comfortable wiring it into Play or Akka HTTP. Do not adopt it if you are starting a new service, because the newest published release is 1.6.0 from 2023-10-16 and the protocol has moved on since, and do not enable the implicit grant or the resource owner password credentials grant on the assumption that they are fine choices. Verify first which Scala version and framework your project uses, because the Play and Akka HTTP integrations live in separate repositories, that your data access can be expressed in the Future returning DataHandler methods or can be wrapped with Future.successful, and how you treat the code challenge method, since the README states that Plain is assumed when a code challenge is supplied without a method, which is the weaker of the two PKCE options.
Frequently asked questions
What is scala-oauth2-provider?
It is the OAuth 2.0 server-side implementation written in Scala, providing server-side functionality and supporting functions for Play Framework and Akka HTTP. The idea originally came from yoichiro's Java oauth2-server project.
Which OAuth grant types does scala-oauth2-provider support?
All of them, according to the README: authorization code grant with PKCE support, resource owner password credentials grant, client credentials grant and implicit grant, plus the Bearer access token type from RFC 6750.
How do I add scala-oauth2-provider to a project?
For Play Framework or Akka HTTP, use the play2-oauth2-provider or akka-http-oauth2-provider projects. For any other framework, add the core library with libraryDependencies ++= Seq("com.nulab-inc" %% "scala-oauth2-core" % "1.6.0") and implement your own provider for your framework.
What do I have to implement to use scala-oauth2-provider?
The DataHandler trait, parameterised by your own user type. It covers client validation, user lookup, creating, storing, refreshing and finding access tokens, and finding or deleting authorization codes, all returning Future. The README says blocking data access can be wrapped with Future.successful.
How does scala-oauth2-provider handle PKCE?
The AuthInfo case class carries optional codeChallenge and codeChallengeMethod fields, and the README states that the method must be either Plain or S256, and that if it is not specified and a code challenge is provided then Plain is assumed, per RFC 7636.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/nulab-scala-oauth2-provider)Was this useful?