Home

Awesome

KOAuth - OAuth 1.0a Provider & Consumer Library in Scala

Archived once, unarchived only to release Scala 2.13 version of it, but considered this project outdated and obsolete.


This library aids calculations according to the OAuth 1.0a specifications for both HTTP server and client.

There are examples of how to use this library here. There are both for consumer, provider, Scala and Java. I recommend trying them out, it will help you more than any readme.

Quick how to

resolvers += "Sonatype Releases" at "https://oss.sonatype.org/content/repositories/releases/"

libraryDependencies += "com.hunorkovacs" %% "koauth" % "2.1.0"

Consumer (Spray)

private def sign(request: HttpRequest, token: String, secret: String) = {
  val requestWithInfo = consumer.createOauthenticatedRequest(KoauthRequest(
      request.method.value, request.uri.toString(), None, None),
      ConsumerKey, ConsumerSecret, token, secret)
  request.withHeaders(RawHeader("Authorization", requestWithInfo.header))
}

sign(pipelining.Get(lastTweetUrl)).flatMap(pipeline(_))

There are also consumer.createRequestTokenRequest() and consumer.createAccessTokenRequest() functions at your disposal.

Provider (Scalatra)

get("/me") {
  val response =
    requestMapper.map(request) flatMap { koauthRequest =>
      provider.oauthenticate(koauthRequest)
    } map {
      case Left(result) => result match {
        case ResponseUnauthorized(body) => Unauthorized("You are treated as a guest.")
        case ResponseBadRequest(body) => BadRequest("You are treated as a guest.")
      }
      case Right(username) => Ok("You are " + username + ".")
    }
  Await.result(response, 4 seconds)
}

Design your controllers:

Define the HTTP paths required by OAuth 1.0a There are also provider.requestToken(), provider.authorizeRequestToken() and provider.accessToken() functions defined to aid you.

post("/oauth/request-token") {
  Await.result(mapCallMap(provider.requestToken), 4 seconds)
}

post("/oauth/access-token") {
  Await.result(mapCallMap(provider.accessToken), 4 seconds)
}

private def mapCallMap(f: KoauthRequest => Future[KoauthResponse]) = {
  requestMapper.map(request)
    .flatMap(f)
    .flatMap(responseMapper.map)
}

Request/response mapping

You will need a RequestMapper that turns your HTTP framework's incoming request to a KoauthRequest:

override def map(source: HttpServletRequest) =
  Future(KoauthRequest(source.getMethod, source.getRequestURL.toString,
    Option(source.getHeader("Authorization")), None))

and a persistence to be able to create a provider:

  val ec = ExecutionContext.Implicits.global
  val provider = ProviderServiceFactory.createProviderService(
    new MyExampleMemoryPersistence(ec), ec)

You should see the example projects, how to map your requests, resonses, and how to handle authorization.

Persistence

When creating a ProviderService, you'll need to define your Persistence for it. This library exposes a trait that you must extend to connect to your database in your own way. To store your tokens & nonces, etc., you could use any kind of underlying database as you whish. There is an in-memory implementation provided, as a guideline, good for practice, not for production use.

There is a test class, PersistenceSpec, that could help you verify if your implementation is correct. It's not an exhausting suite but gives you a basic acknowledgement. Write your test like this:

class YourPersistenceSpec extends PersistenceSpec(
  new YourPersistence(ExecutionContext.Implicits.global))

Authorization

Authorizing a Request Token is done in a custom way and it's not incorporated in this lib as it is not incorporated in the Oauth 1.0a specs. This is usually done by sending a request that contains a username, password and a request token key and the server verifying that and assigning a verifier for the respective token if everything was correct. But using a password is not necessary. One could authorize with facebook for example: if a valid facebook access token could be acquired, one could use that to authorize a request token. This method is totally in your hands. There is an example with a super-simple password-way in the koauth-samples.

Asynchronous

Futures were used trying to make the every call independent and be able to run in parallel. You will need to supply an ExecutionContext for the provider and consumer services:

val ec = play.api.libs.concurrent.Execution.Implicits.defaultContext
val consumer = new ConsumerService(ec)
val provider = new ProviderService(persistence, ec)

Java?

Consumer (using javax.ws.rs HTTP client)

private class LastTweetRoute implements Route {
    public Object handle(Request request, spark.Response response) throws Exception {
        String lastTweetUrl = "https://api.twitter.com/1.1/statuses/user_timeline.json?count=1&include_rts=1&trim_user=true";
        Invocation.Builder builder = http.target(lastTweetUrl).request();
        RequestWithInfo requestWithInfo = consumer.createOauthenticatedRequest(
                        KoauthRequest.apply("GET", lastTweetUrl, Option.<String>empty()),
                CONSUMER_KEY,
                CONSUMER_SECRET,
                accessToken.token(),
                accessToken.secret());
        Invocation invocation = builder.header("Authorization", requestWithInfo.header()).buildGet();

        Response twResponse = invocation.invoke();

        System.out.println("Response: HTTP " + twResponse.getStatus());
        String body = twResponse.readEntity(String.class);
        System.out.println(body);
        return body;
    }
}

Provider (using Spark framework)

private class MeRoute implements Route {
    public Object handle(Request request, Response response) throws Exception {
        KoauthRequest koauthRequest = requestMapper.map(request);
        Either<KoauthResponse, String> authentication = provider.oauthenticate(koauthRequest);
        if (authentication.isLeft()) {
            KoauthResponse left = authentication.left().get();
            if (left.getClass().equals(ResponseUnauthorized.class)) {
                response.status(401);
                return "You are treated as a guest.";
            } else {
                response.status(400);
                return "You are treated as a guest.";
            }
        } else {
            String username = authentication.right().get();
            return "You are " + username + ".";
        }
    }
}

Too much code for a readme, see the examples!

Notes

In a RESTful environment, and with Oauth 1.0a, every request is authenticated, so it's usually a good practice to have your authentication come in as either a filter or a separate proxy application. So instead of the /me method that i defined above, you should have a proxy app that parses every /* request, and just verifies if the request was signed, and if it could be authenticated correctly, attaches this info in a header and passes it on to the real app. There is another example, that does this: koauth-sample-proxy-finagle

Please read the documentation of Oauth 1.0a, understand the process of obtaining an access token and using one for authenticating requests. Take your time. It's not easy for the first read. Implement your controllers for the specification's steps and use the service's methods.

Contributing

Your pull-requests are welcome.

Building and testing locally:

git clone https://github.com/kovacshuni/koauth.git
cd koauth
sbt
compile
test

Publishing (mostly for me)

Detailed help here

sbt
# make sure ~/.sbt/1.0/sonatype.sbt contains your credentials of sonatype, search in your LastPass. File format:
realm=Sonatype Nexus Repository Manager
host=oss.sonatype.org
user=<your username>
password=<your password>

# you have to have a gpg key generated for yourself
# you have to upload that gpg key to a keyserver e.g. keys.openpgp.org or keyserver.ubuntu.com 
# [more explanation on the sbt site](https://www.scala-sbt.org/1.x/docs/Using-Sonatype.htm) 
# because Sonatype will only release if the signed build matches your public key from the keyserver.
# if you have multiple gpg keys, you have to specify which one sbt will use when signing.
# one way i found was to create a file ~/.gnupg/gpg.conf with the content: default-key <key-fingerprint>
# [stackoverflow question Set default key in gpg for signing](https://unix.stackexchange.com/questions/339077/set-default-key-in-gpg-for-signing)

publishSigned
# then on https://oss.sonatype.org/#stagingRepositories close and then release

Owner

Hunor Kovács

hunorkovacs.com

Licence

Licensed under the Apache License, Version 2.0 .