This library aids calculations according to the OAuth 1.0a specifications for both HTTP server and client.
- Provider library: Verifying and responding to HTTP requests according to specifications.
- Consumer library: Complementing HTTP requests to be sent with OAuth parameters.
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.
resolvers += "Sonatype Releases" at "https://oss.sonatype.org/content/repositories/releases/"
libraryDependencies += "com.hunorkovacs" %% "koauth" % "1.1.0"
private def sign(request: HttpRequest, token: String, secret: String) = {
consumer.createOauthenticatedRequest(KoauthRequest(
request.method.value, request.uri.toString(), None, None),
ConsumerKey, ConsumerSecret, token, secret) map { requestWithInfo =>
request.withHeaders(RawHeader("Authorization", requestWithInfo.header))
}
}
sign(pipelining.Get(lastTweetUrl)).flatMap(pipeline(_))
There are also consumer.createRequestTokenRequest()
and consumer.createAccessTokenRequest()
functions at your disposal.
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)
}
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 to /oauth/request-token
- POST to /oauth/access-token
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)
}
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.
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))
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.
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)
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;
}
}
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!
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.
- For consumers, the Signature Base string is exposed to help you debug unhappy situations.
- Always track the releases from GitHub and Maven Central. Pre-built nightly/snapshot versions are not available yet at Maven Central, the master branch is work-in-progress, don't rely on it too much.
- There's a koauth-sync library that doesn't use Futures. It's a wrapper around this, recommended for use in Java or for when you don't want to bother with flatMaps, maps and Awaits.
- I'm planning to implement all this for OAuth 2.0 in the far future.
Just create a pull-request, we'll discuss it, i'll try to be quick.
Building and testing locally:
git clone https://github.com/kovacshuni/koauth.git
cd koauth
sbt
compile
test
Publishing (mostly for me :) ) help
sbt
show */*:pgpSecretRing
# rm that file
show */*:pgpPublicRing
# rm that file
set pgpReadOnly := false
pgp-cmd gen-key
pgp-cmd send-key [email protected] hkp://pool.sks-keyservers.net
# make sure this contains your credentials ~/.sbt/0.13/sonatype.sbt
publishSigned
Hunor Kovács
[email protected]
hunorkovacs.com
Licensed under the Apache License, Version 2.0 .