Bump scribe version to 6.9.0

The plugin classes OAuth20ServiceImpl and OAuth2AccessTokenJsonExtractor
are now replaced with the implementation from scribejava library.

New dependency on jackson-databind and jackson-annotations is required.

Bearer token authorization for request signing is supported now and is
used for providers that known to support it.

Basic client authentication is supported now and is used for providers
that known to support it.

Change-Id: I68717ebb988abe2769ccef9b6f1717bbf7ba9e28
diff --git a/BUILD b/BUILD
index 3b84e91..06a894a 100644
--- a/BUILD
+++ b/BUILD
@@ -21,7 +21,8 @@
     resources = glob(["src/main/resources/**/*"]),
     deps = [
         "@commons-codec//jar:neverlink",
-        "@scribe//jar",
+        "@jackson-databind//jar",
+        "@scribejava-core//jar",
     ],
 )
 
@@ -40,7 +41,6 @@
     visibility = ["//visibility:public"],
     exports = PLUGIN_DEPS + PLUGIN_TEST_DEPS + [
         ":oauth__plugin",
-        "@scribe//jar",
         "@mockito//jar",
     ],
 )
diff --git a/external_plugin_deps.bzl b/external_plugin_deps.bzl
index f5f7852..de7593c 100644
--- a/external_plugin_deps.bzl
+++ b/external_plugin_deps.bzl
@@ -1,10 +1,24 @@
 load("//tools/bzl:maven_jar.bzl", "maven_jar")
 
 def external_plugin_deps(omit_commons_codec = True):
+    JACKSON_VERS = "2.10.2"
     maven_jar(
-        name = "scribe",
-        artifact = "org.scribe:scribe:1.3.7",
-        sha1 = "583921bed46635d9f529ef5f14f7c9e83367bc6e",
+        name = "scribejava-core",
+        artifact = "com.github.scribejava:scribejava-core:6.9.0",
+        sha1 = "ed761f450d8382f75787e8fee9ae52e7ec768747",
+    )
+    maven_jar(
+        name = "jackson-annotations",
+        artifact = "com.fasterxml.jackson.core:jackson-annotations:" + JACKSON_VERS,
+        sha1 = "3a13b6105946541b8d4181a0506355b5fae63260",
+    )
+    maven_jar(
+        name = "jackson-databind",
+        artifact = "com.fasterxml.jackson.core:jackson-databind:" + JACKSON_VERS,
+        sha1 = "0528de95f198afafbcfb0c09d2e43b6e0ea663ec",
+        deps = [
+            "@jackson-annotations//jar",
+        ],
     )
     maven_jar(
         name = "mockito",
diff --git a/src/main/java/com/googlesource/gerrit/plugins/oauth/AirVantageApi.java b/src/main/java/com/googlesource/gerrit/plugins/oauth/AirVantageApi.java
index 02f4bd5..2e769a5 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/oauth/AirVantageApi.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/oauth/AirVantageApi.java
@@ -14,43 +14,31 @@
 
 package com.googlesource.gerrit.plugins.oauth;
 
-import static java.lang.String.format;
-
-import org.scribe.builder.api.DefaultApi20;
-import org.scribe.extractors.AccessTokenExtractor;
-import org.scribe.extractors.JsonTokenExtractor;
-import org.scribe.model.OAuthConfig;
-import org.scribe.model.Verb;
-import org.scribe.oauth.OAuthService;
+import com.github.scribejava.core.builder.api.DefaultApi20;
+import com.github.scribejava.core.extractors.OAuth2AccessTokenExtractor;
+import com.github.scribejava.core.extractors.TokenExtractor;
+import com.github.scribejava.core.model.OAuth2AccessToken;
+import com.github.scribejava.core.oauth2.bearersignature.BearerSignature;
+import com.github.scribejava.core.oauth2.bearersignature.BearerSignatureURIQueryParameter;
 
 public class AirVantageApi extends DefaultApi20 {
-
-  private static final String AUTHORIZE_URL =
-      "https://eu.airvantage.net/api/oauth/authorize?client_id=%s&response_type=code";
-  private static final String ACCESS_TOKEN_ENDPOINT = "https://eu.airvantage.net/api/oauth/token";
-
   @Override
-  public String getAuthorizationUrl(OAuthConfig config) {
-    return format(AUTHORIZE_URL, config.getApiKey());
+  public String getAuthorizationBaseUrl() {
+    return "https://eu.airvantage.net/api/oauth/authorize";
   }
 
   @Override
   public String getAccessTokenEndpoint() {
-    return ACCESS_TOKEN_ENDPOINT;
+    return "https://eu.airvantage.net/api/oauth/token";
   }
 
   @Override
-  public Verb getAccessTokenVerb() {
-    return Verb.POST;
+  public BearerSignature getBearerSignature() {
+    return BearerSignatureURIQueryParameter.instance();
   }
 
   @Override
-  public AccessTokenExtractor getAccessTokenExtractor() {
-    return new JsonTokenExtractor();
-  }
-
-  @Override
-  public OAuthService createService(OAuthConfig config) {
-    return new OAuth20ServiceImpl(this, config);
+  public TokenExtractor<OAuth2AccessToken> getAccessTokenExtractor() {
+    return OAuth2AccessTokenExtractor.instance();
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/oauth/AirVantageOAuthService.java b/src/main/java/com/googlesource/gerrit/plugins/oauth/AirVantageOAuthService.java
index 25edf2e..1fadd21 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/oauth/AirVantageOAuthService.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/oauth/AirVantageOAuthService.java
@@ -18,6 +18,12 @@
 import static javax.servlet.http.HttpServletResponse.SC_OK;
 import static org.slf4j.LoggerFactory.getLogger;
 
+import com.github.scribejava.core.builder.ServiceBuilder;
+import com.github.scribejava.core.model.OAuth2AccessToken;
+import com.github.scribejava.core.model.OAuthRequest;
+import com.github.scribejava.core.model.Response;
+import com.github.scribejava.core.model.Verb;
+import com.github.scribejava.core.oauth.OAuth20Service;
 import com.google.common.base.CharMatcher;
 import com.google.gerrit.extensions.annotations.PluginName;
 import com.google.gerrit.extensions.auth.oauth.OAuthServiceProvider;
@@ -33,13 +39,7 @@
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
 import java.io.IOException;
-import org.scribe.builder.ServiceBuilder;
-import org.scribe.model.OAuthRequest;
-import org.scribe.model.Response;
-import org.scribe.model.Token;
-import org.scribe.model.Verb;
-import org.scribe.model.Verifier;
-import org.scribe.oauth.OAuthService;
+import java.util.concurrent.ExecutionException;
 import org.slf4j.Logger;
 
 @Singleton
@@ -49,7 +49,7 @@
   private static final String AV_PROVIDER_PREFIX = "airvantage-oauth:";
   private static final String PROTECTED_RESOURCE_URL =
       "https://eu.airvantage.net/api/v1/users/current";
-  private final OAuthService service;
+  private final OAuth20Service service;
 
   @Inject
   AirVantageOAuthService(
@@ -60,44 +60,47 @@
     String canonicalWebUrl = CharMatcher.is('/').trimTrailingFrom(urlProvider.get()) + "/";
 
     service =
-        new ServiceBuilder()
-            .provider(AirVantageApi.class)
-            .apiKey(cfg.getString(InitOAuth.CLIENT_ID))
+        new ServiceBuilder(cfg.getString(InitOAuth.CLIENT_ID))
             .apiSecret(cfg.getString(InitOAuth.CLIENT_SECRET))
             .callback(canonicalWebUrl + "oauth")
-            .build();
+            .build(new AirVantageApi());
   }
 
   @Override
   public OAuthUserInfo getUserInfo(OAuthToken token) throws IOException {
     OAuthRequest request = new OAuthRequest(Verb.GET, PROTECTED_RESOURCE_URL);
-    Token t = new Token(token.getToken(), token.getSecret(), token.getRaw());
+    OAuth2AccessToken t = new OAuth2AccessToken(token.getToken(), token.getRaw());
     service.signRequest(t, request);
-    Response response = request.send();
-    if (response.getCode() != SC_OK) {
-      throw new IOException(
-          String.format(
-              "Status %s (%s) for request %s",
-              response.getCode(), response.getBody(), request.getUrl()));
-    }
-    JsonElement userJson = JSON.newGson().fromJson(response.getBody(), JsonElement.class);
-    if (log.isDebugEnabled()) {
-      log.debug("User info response: {}", response.getBody());
-    }
-    if (userJson.isJsonObject()) {
-      JsonObject jsonObject = userJson.getAsJsonObject();
-      JsonElement id = jsonObject.get("uid");
-      if (id == null || id.isJsonNull()) {
-        throw new IOException("Response doesn't contain uid field");
+
+    JsonElement userJson = null;
+    try (Response response = service.execute(request)) {
+      if (response.getCode() != SC_OK) {
+        throw new IOException(
+            String.format(
+                "Status %s (%s) for request %s",
+                response.getCode(), response.getBody(), request.getUrl()));
       }
-      JsonElement email = jsonObject.get("email");
-      JsonElement name = jsonObject.get("name");
-      return new OAuthUserInfo(
-          AV_PROVIDER_PREFIX + id.getAsString(),
-          null,
-          email.getAsString(),
-          name.getAsString(),
-          id.getAsString());
+      userJson = JSON.newGson().fromJson(response.getBody(), JsonElement.class);
+      if (log.isDebugEnabled()) {
+        log.debug("User info response: {}", response.getBody());
+      }
+      if (userJson.isJsonObject()) {
+        JsonObject jsonObject = userJson.getAsJsonObject();
+        JsonElement id = jsonObject.get("uid");
+        if (id == null || id.isJsonNull()) {
+          throw new IOException("Response doesn't contain uid field");
+        }
+        JsonElement email = jsonObject.get("email");
+        JsonElement name = jsonObject.get("name");
+        return new OAuthUserInfo(
+            AV_PROVIDER_PREFIX + id.getAsString(),
+            null,
+            email.getAsString(),
+            name.getAsString(),
+            id.getAsString());
+      }
+    } catch (ExecutionException | InterruptedException e) {
+      throw new RuntimeException("Cannot retrieve user info resource", e);
     }
 
     throw new IOException(String.format("Invalid JSON '%s': not a JSON Object", userJson));
@@ -105,14 +108,20 @@
 
   @Override
   public OAuthToken getAccessToken(OAuthVerifier rv) {
-    Verifier vi = new Verifier(rv.getValue());
-    Token to = service.getAccessToken(null, vi);
-    return new OAuthToken(to.getToken(), to.getSecret(), to.getRawResponse());
+    try {
+      OAuth2AccessToken accessToken = service.getAccessToken(rv.getValue());
+      return new OAuthToken(
+          accessToken.getAccessToken(), accessToken.getTokenType(), accessToken.getRawResponse());
+    } catch (InterruptedException | ExecutionException | IOException e) {
+      String msg = "Cannot retrieve access token";
+      log.error(msg, e);
+      throw new RuntimeException(msg, e);
+    }
   }
 
   @Override
   public String getAuthorizationUrl() {
-    return service.getAuthorizationUrl(null);
+    return service.getAuthorizationUrl();
   }
 
   @Override
diff --git a/src/main/java/com/googlesource/gerrit/plugins/oauth/BitbucketApi.java b/src/main/java/com/googlesource/gerrit/plugins/oauth/BitbucketApi.java
index a3fff4a..a68ae4b 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/oauth/BitbucketApi.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/oauth/BitbucketApi.java
@@ -14,137 +14,31 @@
 
 package com.googlesource.gerrit.plugins.oauth;
 
-import static com.google.gerrit.server.OutputFormat.JSON;
-import static java.lang.String.format;
-import static javax.servlet.http.HttpServletResponse.SC_OK;
-import static org.scribe.model.OAuthConstants.ACCESS_TOKEN;
-import static org.scribe.model.OAuthConstants.CODE;
-
-import com.google.common.io.BaseEncoding;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
-import org.scribe.builder.api.DefaultApi20;
-import org.scribe.exceptions.OAuthException;
-import org.scribe.extractors.AccessTokenExtractor;
-import org.scribe.model.OAuthConfig;
-import org.scribe.model.OAuthRequest;
-import org.scribe.model.Response;
-import org.scribe.model.Token;
-import org.scribe.model.Verb;
-import org.scribe.model.Verifier;
-import org.scribe.oauth.OAuthService;
+import com.github.scribejava.core.builder.api.DefaultApi20;
+import com.github.scribejava.core.extractors.OAuth2AccessTokenExtractor;
+import com.github.scribejava.core.extractors.TokenExtractor;
+import com.github.scribejava.core.model.OAuth2AccessToken;
+import com.github.scribejava.core.oauth2.bearersignature.BearerSignature;
+import com.github.scribejava.core.oauth2.bearersignature.BearerSignatureURIQueryParameter;
 
 public class BitbucketApi extends DefaultApi20 {
-
-  private static final String AUTHORIZE_URL =
-      "https://bitbucket.org/site/oauth2/authorize?client_id=%s&response_type=code";
-  private static final String ACCESS_TOKEN_ENDPOINT =
-      "https://bitbucket.org/site/oauth2/access_token";
-
-  public BitbucketApi() {}
-
   @Override
-  public String getAuthorizationUrl(OAuthConfig config) {
-    return format(AUTHORIZE_URL, config.getApiKey());
+  public String getAuthorizationBaseUrl() {
+    return "https://bitbucket.org/site/oauth2/authorize";
   }
 
   @Override
   public String getAccessTokenEndpoint() {
-    return ACCESS_TOKEN_ENDPOINT;
+    return "https://bitbucket.org/site/oauth2/access_token";
   }
 
   @Override
-  public Verb getAccessTokenVerb() {
-    return Verb.POST;
+  public BearerSignature getBearerSignature() {
+    return BearerSignatureURIQueryParameter.instance();
   }
 
   @Override
-  public OAuthService createService(OAuthConfig config) {
-    return new BitbucketOAuthService(this, config);
-  }
-
-  @Override
-  public AccessTokenExtractor getAccessTokenExtractor() {
-    return new BitbucketTokenExtractor();
-  }
-
-  private static final class BitbucketOAuthService implements OAuthService {
-    private static final String VERSION = "2.0";
-
-    private static final String GRANT_TYPE = "grant_type";
-    private static final String GRANT_TYPE_VALUE = "authorization_code";
-
-    private final DefaultApi20 api;
-    private final OAuthConfig config;
-
-    private BitbucketOAuthService(DefaultApi20 api, OAuthConfig config) {
-      this.config = config;
-      this.api = api;
-    }
-
-    @Override
-    public Token getAccessToken(Token token, Verifier verifier) {
-      OAuthRequest request =
-          new OAuthRequest(api.getAccessTokenVerb(), api.getAccessTokenEndpoint());
-      request.addHeader("Authorization", prepareAuthorizationHeaderValue());
-      request.addBodyParameter(GRANT_TYPE, GRANT_TYPE_VALUE);
-      request.addBodyParameter(CODE, verifier.getValue());
-      Response response = request.send();
-      if (response.getCode() == SC_OK) {
-        Token t = api.getAccessTokenExtractor().extract(response.getBody());
-        return new Token(t.getToken(), config.getApiSecret());
-      }
-
-      throw new OAuthException(
-          String.format(
-              "Error response received: %s, HTTP status: %s",
-              response.getBody(), response.getCode()));
-    }
-
-    private String prepareAuthorizationHeaderValue() {
-      String value = String.format("%s:%s", config.getApiKey(), config.getApiSecret());
-      String valueBase64 = BaseEncoding.base64().encode(value.getBytes());
-      return String.format("Basic %s", valueBase64);
-    }
-
-    @Override
-    public Token getRequestToken() {
-      throw new UnsupportedOperationException(
-          "Unsupported operation, please use 'getAuthorizationUrl' and redirect your users there");
-    }
-
-    @Override
-    public String getVersion() {
-      return VERSION;
-    }
-
-    @Override
-    public void signRequest(Token token, OAuthRequest request) {
-      request.addQuerystringParameter(ACCESS_TOKEN, token.getToken());
-    }
-
-    @Override
-    public String getAuthorizationUrl(Token token) {
-      return api.getAuthorizationUrl(config);
-    }
-  }
-
-  private static final class BitbucketTokenExtractor implements AccessTokenExtractor {
-
-    @Override
-    public Token extract(String response) {
-      JsonElement json = JSON.newGson().fromJson(response, JsonElement.class);
-      if (json.isJsonObject()) {
-        JsonObject jsonObject = json.getAsJsonObject();
-        JsonElement id = jsonObject.get(ACCESS_TOKEN);
-        if (id == null || id.isJsonNull()) {
-          throw new OAuthException("Response doesn't contain 'access_token' field");
-        }
-        JsonElement accessToken = jsonObject.get(ACCESS_TOKEN);
-        return new Token(accessToken.getAsString(), "");
-      }
-
-      throw new OAuthException(String.format("Invalid JSON '%s': not a JSON Object", json));
-    }
+  public TokenExtractor<OAuth2AccessToken> getAccessTokenExtractor() {
+    return OAuth2AccessTokenExtractor.instance();
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/oauth/BitbucketOAuthService.java b/src/main/java/com/googlesource/gerrit/plugins/oauth/BitbucketOAuthService.java
index 55680f2..60d156c 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/oauth/BitbucketOAuthService.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/oauth/BitbucketOAuthService.java
@@ -18,6 +18,12 @@
 import static javax.servlet.http.HttpServletResponse.SC_OK;
 import static org.slf4j.LoggerFactory.getLogger;
 
+import com.github.scribejava.core.builder.ServiceBuilder;
+import com.github.scribejava.core.model.OAuth2AccessToken;
+import com.github.scribejava.core.model.OAuthRequest;
+import com.github.scribejava.core.model.Response;
+import com.github.scribejava.core.model.Verb;
+import com.github.scribejava.core.oauth.OAuth20Service;
 import com.google.common.base.CharMatcher;
 import com.google.gerrit.extensions.annotations.PluginName;
 import com.google.gerrit.extensions.auth.oauth.OAuthServiceProvider;
@@ -33,13 +39,7 @@
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
 import java.io.IOException;
-import org.scribe.builder.ServiceBuilder;
-import org.scribe.model.OAuthRequest;
-import org.scribe.model.Response;
-import org.scribe.model.Token;
-import org.scribe.model.Verb;
-import org.scribe.model.Verifier;
-import org.scribe.oauth.OAuthService;
+import java.util.concurrent.ExecutionException;
 import org.slf4j.Logger;
 
 @Singleton
@@ -49,7 +49,7 @@
   private static final String BITBUCKET_PROVIDER_PREFIX = "bitbucket-oauth:";
   private static final String PROTECTED_RESOURCE_URL = "https://bitbucket.org/api/1.0/user/";
   private final boolean fixLegacyUserId;
-  private final OAuthService service;
+  private final OAuth20Service service;
 
   @Inject
   BitbucketOAuthService(
@@ -61,46 +61,50 @@
     String canonicalWebUrl = CharMatcher.is('/').trimTrailingFrom(urlProvider.get()) + "/";
     fixLegacyUserId = cfg.getBoolean(InitOAuth.FIX_LEGACY_USER_ID, false);
     service =
-        new ServiceBuilder()
-            .provider(BitbucketApi.class)
-            .apiKey(cfg.getString(InitOAuth.CLIENT_ID))
+        new ServiceBuilder(cfg.getString(InitOAuth.CLIENT_ID))
             .apiSecret(cfg.getString(InitOAuth.CLIENT_SECRET))
             .callback(canonicalWebUrl + "oauth")
-            .build();
+            .build(new BitbucketApi());
   }
 
   @Override
   public OAuthUserInfo getUserInfo(OAuthToken token) throws IOException {
     OAuthRequest request = new OAuthRequest(Verb.GET, PROTECTED_RESOURCE_URL);
-    Token t = new Token(token.getToken(), token.getSecret(), token.getRaw());
+    OAuth2AccessToken t = new OAuth2AccessToken(token.getToken(), token.getRaw());
     service.signRequest(t, request);
-    Response response = request.send();
-    if (response.getCode() != SC_OK) {
-      throw new IOException(
-          String.format(
-              "Status %s (%s) for request %s",
-              response.getCode(), response.getBody(), request.getUrl()));
-    }
-    JsonElement userJson = JSON.newGson().fromJson(response.getBody(), JsonElement.class);
-    if (log.isDebugEnabled()) {
-      log.debug("User info response: {}", response.getBody());
-    }
-    if (userJson.isJsonObject()) {
-      JsonObject jsonObject = userJson.getAsJsonObject();
-      JsonObject userObject = jsonObject.getAsJsonObject("user");
-      if (userObject == null || userObject.isJsonNull()) {
-        throw new IOException("Response doesn't contain 'user' field");
-      }
-      JsonElement usernameElement = userObject.get("username");
-      String username = usernameElement.getAsString();
 
-      JsonElement displayName = jsonObject.get("display_name");
-      return new OAuthUserInfo(
-          BITBUCKET_PROVIDER_PREFIX + username,
-          username,
-          null,
-          displayName == null || displayName.isJsonNull() ? null : displayName.getAsString(),
-          fixLegacyUserId ? username : null);
+    JsonElement userJson = null;
+    try (Response response = service.execute(request)) {
+
+      if (response.getCode() != SC_OK) {
+        throw new IOException(
+            String.format(
+                "Status %s (%s) for request %s",
+                response.getCode(), response.getBody(), request.getUrl()));
+      }
+      userJson = JSON.newGson().fromJson(response.getBody(), JsonElement.class);
+      if (log.isDebugEnabled()) {
+        log.debug("User info response: {}", response.getBody());
+      }
+      if (userJson.isJsonObject()) {
+        JsonObject jsonObject = userJson.getAsJsonObject();
+        JsonObject userObject = jsonObject.getAsJsonObject("user");
+        if (userObject == null || userObject.isJsonNull()) {
+          throw new IOException("Response doesn't contain 'user' field");
+        }
+        JsonElement usernameElement = userObject.get("username");
+        String username = usernameElement.getAsString();
+
+        JsonElement displayName = jsonObject.get("display_name");
+        return new OAuthUserInfo(
+            BITBUCKET_PROVIDER_PREFIX + username,
+            username,
+            null,
+            displayName == null || displayName.isJsonNull() ? null : displayName.getAsString(),
+            fixLegacyUserId ? username : null);
+      }
+    } catch (ExecutionException | InterruptedException e) {
+      throw new RuntimeException("Cannot retrieve user info resource", e);
     }
 
     throw new IOException(String.format("Invalid JSON '%s': not a JSON Object", userJson));
@@ -108,14 +112,20 @@
 
   @Override
   public OAuthToken getAccessToken(OAuthVerifier rv) {
-    Verifier vi = new Verifier(rv.getValue());
-    Token to = service.getAccessToken(null, vi);
-    return new OAuthToken(to.getToken(), to.getSecret(), to.getRawResponse());
+    try {
+      OAuth2AccessToken accessToken = service.getAccessToken(rv.getValue());
+      return new OAuthToken(
+          accessToken.getAccessToken(), accessToken.getTokenType(), accessToken.getRawResponse());
+    } catch (InterruptedException | ExecutionException | IOException e) {
+      String msg = "Cannot retrieve access token";
+      log.error(msg, e);
+      throw new RuntimeException(msg, e);
+    }
   }
 
   @Override
   public String getAuthorizationUrl() {
-    return service.getAuthorizationUrl(null);
+    return service.getAuthorizationUrl();
   }
 
   @Override
diff --git a/src/main/java/com/googlesource/gerrit/plugins/oauth/CasApi.java b/src/main/java/com/googlesource/gerrit/plugins/oauth/CasApi.java
index 76d4011..450549f 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/oauth/CasApi.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/oauth/CasApi.java
@@ -14,15 +14,15 @@
 
 package com.googlesource.gerrit.plugins.oauth;
 
-import org.scribe.builder.api.DefaultApi20;
-import org.scribe.model.OAuthConfig;
-import org.scribe.model.Verb;
-import org.scribe.oauth.OAuthService;
-import org.scribe.utils.OAuthEncoder;
+import com.github.scribejava.core.builder.api.DefaultApi20;
+import com.github.scribejava.core.extractors.OAuth2AccessTokenExtractor;
+import com.github.scribejava.core.extractors.TokenExtractor;
+import com.github.scribejava.core.model.OAuth2AccessToken;
+import com.github.scribejava.core.oauth2.bearersignature.BearerSignature;
+import com.github.scribejava.core.oauth2.bearersignature.BearerSignatureURIQueryParameter;
 
 public class CasApi extends DefaultApi20 {
-  private static final String AUTHORIZE_URL =
-      "%s/oauth2.0/authorize?response_type=code&client_id=%s&redirect_uri=%s";
+  private static final String AUTHORIZE_URL = "%s/oauth2.0/authorize";
 
   private final String rootUrl;
 
@@ -36,18 +36,17 @@
   }
 
   @Override
-  public String getAuthorizationUrl(OAuthConfig config) {
-    return String.format(
-        AUTHORIZE_URL, rootUrl, config.getApiKey(), OAuthEncoder.encode(config.getCallback()));
+  public String getAuthorizationBaseUrl() {
+    return String.format(AUTHORIZE_URL, rootUrl);
   }
 
   @Override
-  public Verb getAccessTokenVerb() {
-    return Verb.POST;
+  public BearerSignature getBearerSignature() {
+    return BearerSignatureURIQueryParameter.instance();
   }
 
   @Override
-  public OAuthService createService(OAuthConfig config) {
-    return new OAuth20ServiceImpl(this, config);
+  public TokenExtractor<OAuth2AccessToken> getAccessTokenExtractor() {
+    return OAuth2AccessTokenExtractor.instance();
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/oauth/CasOAuthService.java b/src/main/java/com/googlesource/gerrit/plugins/oauth/CasOAuthService.java
index f360cd9..14fc080 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/oauth/CasOAuthService.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/oauth/CasOAuthService.java
@@ -14,13 +14,20 @@
 
 package com.googlesource.gerrit.plugins.oauth;
 
+import static com.google.gerrit.server.OutputFormat.JSON;
+
+import com.github.scribejava.core.builder.ServiceBuilder;
+import com.github.scribejava.core.model.OAuth2AccessToken;
+import com.github.scribejava.core.model.OAuthRequest;
+import com.github.scribejava.core.model.Response;
+import com.github.scribejava.core.model.Verb;
+import com.github.scribejava.core.oauth.OAuth20Service;
 import com.google.common.base.CharMatcher;
 import com.google.gerrit.extensions.annotations.PluginName;
 import com.google.gerrit.extensions.auth.oauth.OAuthServiceProvider;
 import com.google.gerrit.extensions.auth.oauth.OAuthToken;
 import com.google.gerrit.extensions.auth.oauth.OAuthUserInfo;
 import com.google.gerrit.extensions.auth.oauth.OAuthVerifier;
-import com.google.gerrit.server.OutputFormat;
 import com.google.gerrit.server.config.CanonicalWebUrl;
 import com.google.gerrit.server.config.PluginConfig;
 import com.google.gerrit.server.config.PluginConfigFactory;
@@ -33,14 +40,8 @@
 import com.google.inject.Singleton;
 import java.io.IOException;
 import java.net.URI;
+import java.util.concurrent.ExecutionException;
 import javax.servlet.http.HttpServletResponse;
-import org.scribe.builder.ServiceBuilder;
-import org.scribe.model.OAuthRequest;
-import org.scribe.model.Response;
-import org.scribe.model.Token;
-import org.scribe.model.Verb;
-import org.scribe.model.Verifier;
-import org.scribe.oauth.OAuthService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -53,7 +54,7 @@
 
   private final String rootUrl;
   private final boolean fixLegacyUserId;
-  private final OAuthService service;
+  private final OAuth20Service service;
 
   @Inject
   CasOAuthService(
@@ -68,81 +69,82 @@
     String canonicalWebUrl = CharMatcher.is('/').trimTrailingFrom(urlProvider.get()) + "/";
     fixLegacyUserId = cfg.getBoolean(InitOAuth.FIX_LEGACY_USER_ID, false);
     service =
-        new ServiceBuilder()
-            .provider(new CasApi(rootUrl))
-            .apiKey(cfg.getString(InitOAuth.CLIENT_ID))
+        new ServiceBuilder(cfg.getString(InitOAuth.CLIENT_ID))
             .apiSecret(cfg.getString(InitOAuth.CLIENT_SECRET))
             .callback(canonicalWebUrl + "oauth")
-            .build();
+            .build(new CasApi(rootUrl));
   }
 
   @Override
   public OAuthUserInfo getUserInfo(OAuthToken token) throws IOException {
-    final String protectedResourceUrl = String.format(PROTECTED_RESOURCE_URL, rootUrl);
-    OAuthRequest request = new OAuthRequest(Verb.GET, protectedResourceUrl);
-    Token t = new Token(token.getToken(), token.getSecret(), token.getRaw());
+    OAuthRequest request =
+        new OAuthRequest(Verb.GET, String.format(PROTECTED_RESOURCE_URL, rootUrl));
+    OAuth2AccessToken t = new OAuth2AccessToken(token.getToken(), token.getRaw());
     service.signRequest(t, request);
 
-    Response response = request.send();
-    if (response.getCode() != HttpServletResponse.SC_OK) {
-      throw new IOException(
-          String.format(
-              "Status %s (%s) for request %s",
-              response.getCode(), response.getBody(), request.getUrl()));
-    }
-
-    if (log.isDebugEnabled()) {
-      log.debug("User info response: {}", response.getBody());
-    }
-
-    JsonElement userJson =
-        OutputFormat.JSON.newGson().fromJson(response.getBody(), JsonElement.class);
-    if (!userJson.isJsonObject()) {
-      throw new IOException(String.format("Invalid JSON '%s': not a JSON Object", userJson));
-    }
-    JsonObject jsonObject = userJson.getAsJsonObject();
-
-    JsonElement id = jsonObject.get("id");
-    if (id == null || id.isJsonNull()) {
-      throw new IOException(String.format("CAS response missing id: %s", response.getBody()));
-    }
-
-    JsonElement attrListJson = jsonObject.get("attributes");
-    if (attrListJson == null) {
-      throw new IOException(
-          String.format("CAS response missing attributes: %s", response.getBody()));
-    }
-
-    String email = null, name = null, login = null;
-
-    if (attrListJson.isJsonArray()) {
-      // It is possible for CAS to be configured to not return any attributes (email, name, login),
-      // in which case,
-      // CAS returns an empty JSON object "attributes":{}, rather than "null" or an empty JSON array
-      // "attributes": []
-
-      JsonArray attrJson = attrListJson.getAsJsonArray();
-      for (JsonElement elem : attrJson) {
-        if (elem == null || !elem.isJsonObject()) {
-          throw new IOException(String.format("Invalid JSON '%s': not a JSON Object", elem));
-        }
-        JsonObject obj = elem.getAsJsonObject();
-
-        String property = getStringElement(obj, "email");
-        if (property != null) email = property;
-        property = getStringElement(obj, "name");
-        if (property != null) name = property;
-        property = getStringElement(obj, "login");
-        if (property != null) login = property;
+    try (Response response = service.execute(request)) {
+      if (response.getCode() != HttpServletResponse.SC_OK) {
+        throw new IOException(
+            String.format(
+                "Status %s (%s) for request %s",
+                response.getCode(), response.getBody(), request.getUrl()));
       }
-    }
 
-    return new OAuthUserInfo(
-        CAS_PROVIDER_PREFIX + id.getAsString(),
-        login,
-        email,
-        name,
-        fixLegacyUserId ? id.getAsString() : null);
+      if (log.isDebugEnabled()) {
+        log.debug("User info response: {}", response.getBody());
+      }
+
+      JsonElement userJson = JSON.newGson().fromJson(response.getBody(), JsonElement.class);
+      if (!userJson.isJsonObject()) {
+        throw new IOException(String.format("Invalid JSON '%s': not a JSON Object", userJson));
+      }
+      JsonObject jsonObject = userJson.getAsJsonObject();
+
+      JsonElement id = jsonObject.get("id");
+      if (id == null || id.isJsonNull()) {
+        throw new IOException(String.format("CAS response missing id: %s", response.getBody()));
+      }
+
+      JsonElement attrListJson = jsonObject.get("attributes");
+      if (attrListJson == null) {
+        throw new IOException(
+            String.format("CAS response missing attributes: %s", response.getBody()));
+      }
+
+      String email = null, name = null, login = null;
+      if (attrListJson.isJsonArray()) {
+        // It is possible for CAS to be configured to not return any attributes (email, name,
+        // login),
+        // in which case,
+        // CAS returns an empty JSON object "attributes":{}, rather than "null" or an empty JSON
+        // array
+        // "attributes": []
+
+        JsonArray attrJson = attrListJson.getAsJsonArray();
+        for (JsonElement elem : attrJson) {
+          if (elem == null || !elem.isJsonObject()) {
+            throw new IOException(String.format("Invalid JSON '%s': not a JSON Object", elem));
+          }
+          JsonObject obj = elem.getAsJsonObject();
+
+          String property = getStringElement(obj, "email");
+          if (property != null) email = property;
+          property = getStringElement(obj, "name");
+          if (property != null) name = property;
+          property = getStringElement(obj, "login");
+          if (property != null) login = property;
+        }
+      }
+
+      return new OAuthUserInfo(
+          CAS_PROVIDER_PREFIX + id.getAsString(),
+          login,
+          email,
+          name,
+          fixLegacyUserId ? id.getAsString() : null);
+    } catch (ExecutionException | InterruptedException e) {
+      throw new RuntimeException("Cannot retrieve user info resource", e);
+    }
   }
 
   private String getStringElement(JsonObject o, String name) {
@@ -154,14 +156,20 @@
 
   @Override
   public OAuthToken getAccessToken(OAuthVerifier rv) {
-    Verifier vi = new Verifier(rv.getValue());
-    Token to = service.getAccessToken(null, vi);
-    return new OAuthToken(to.getToken(), to.getSecret(), to.getRawResponse());
+    try {
+      OAuth2AccessToken accessToken = service.getAccessToken(rv.getValue());
+      return new OAuthToken(
+          accessToken.getAccessToken(), accessToken.getTokenType(), accessToken.getRawResponse());
+    } catch (InterruptedException | ExecutionException | IOException e) {
+      String msg = "Cannot retrieve access token";
+      log.error(msg, e);
+      throw new RuntimeException(msg, e);
+    }
   }
 
   @Override
   public String getAuthorizationUrl() {
-    return service.getAuthorizationUrl(null);
+    return service.getAuthorizationUrl();
   }
 
   @Override
diff --git a/src/main/java/com/googlesource/gerrit/plugins/oauth/DexApi.java b/src/main/java/com/googlesource/gerrit/plugins/oauth/DexApi.java
index 2386e24..560aed7 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/oauth/DexApi.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/oauth/DexApi.java
@@ -14,18 +14,16 @@
 
 package com.googlesource.gerrit.plugins.oauth;
 
-import org.scribe.builder.api.DefaultApi20;
-import org.scribe.extractors.AccessTokenExtractor;
-import org.scribe.extractors.JsonTokenExtractor;
-import org.scribe.model.OAuthConfig;
-import org.scribe.model.Verb;
-import org.scribe.oauth.OAuthService;
-import org.scribe.utils.OAuthEncoder;
+import com.github.scribejava.core.builder.api.DefaultApi20;
+import com.github.scribejava.core.extractors.OAuth2AccessTokenExtractor;
+import com.github.scribejava.core.extractors.TokenExtractor;
+import com.github.scribejava.core.model.OAuth2AccessToken;
+import com.github.scribejava.core.oauth2.bearersignature.BearerSignature;
+import com.github.scribejava.core.oauth2.bearersignature.BearerSignatureURIQueryParameter;
 
 public class DexApi extends DefaultApi20 {
 
-  private static final String AUTHORIZE_URL =
-      "%s/dex/auth?client_id=%s&response_type=code&redirect_uri=%s&scope=%s";
+  private static final String AUTHORIZE_URL = "%s/dex/auth";
 
   private final String rootUrl;
 
@@ -34,13 +32,8 @@
   }
 
   @Override
-  public String getAuthorizationUrl(OAuthConfig config) {
-    return String.format(
-        AUTHORIZE_URL,
-        rootUrl,
-        config.getApiKey(),
-        OAuthEncoder.encode(config.getCallback()),
-        config.getScope().replaceAll(" ", "+"));
+  public String getAuthorizationBaseUrl() {
+    return String.format(AUTHORIZE_URL, rootUrl);
   }
 
   @Override
@@ -49,17 +42,12 @@
   }
 
   @Override
-  public Verb getAccessTokenVerb() {
-    return Verb.POST;
+  public BearerSignature getBearerSignature() {
+    return BearerSignatureURIQueryParameter.instance();
   }
 
   @Override
-  public OAuthService createService(OAuthConfig config) {
-    return new OAuth20ServiceImpl(this, config);
-  }
-
-  @Override
-  public AccessTokenExtractor getAccessTokenExtractor() {
-    return new JsonTokenExtractor();
+  public TokenExtractor<OAuth2AccessToken> getAccessTokenExtractor() {
+    return OAuth2AccessTokenExtractor.instance();
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/oauth/DexOAuthService.java b/src/main/java/com/googlesource/gerrit/plugins/oauth/DexOAuthService.java
index c899e5e..0570896 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/oauth/DexOAuthService.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/oauth/DexOAuthService.java
@@ -16,6 +16,9 @@
 
 import static com.google.gerrit.server.OutputFormat.JSON;
 
+import com.github.scribejava.core.builder.ServiceBuilder;
+import com.github.scribejava.core.model.OAuth2AccessToken;
+import com.github.scribejava.core.oauth.OAuth20Service;
 import com.google.common.base.CharMatcher;
 import com.google.common.base.Preconditions;
 import com.google.gerrit.extensions.annotations.PluginName;
@@ -34,18 +37,18 @@
 import com.google.inject.Singleton;
 import java.io.IOException;
 import java.net.URI;
+import java.util.concurrent.ExecutionException;
 import org.apache.commons.codec.binary.Base64;
-import org.scribe.builder.ServiceBuilder;
-import org.scribe.model.Token;
-import org.scribe.model.Verifier;
-import org.scribe.oauth.OAuthService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 @Singleton
 public class DexOAuthService implements OAuthServiceProvider {
+  private static final Logger log = LoggerFactory.getLogger(DexOAuthService.class);
 
   static final String CONFIG_SUFFIX = "-dex-oauth";
   private static final String DEX_PROVIDER_PREFIX = "dex-oauth:";
-  private final OAuthService service;
+  private final OAuth20Service service;
   private final String rootUrl;
   private final String domain;
   private final String serviceName;
@@ -66,13 +69,11 @@
     serviceName = cfg.getString(InitOAuth.SERVICE_NAME, "Dex OAuth2");
 
     service =
-        new ServiceBuilder()
-            .provider(new DexApi(rootUrl))
-            .apiKey(cfg.getString(InitOAuth.CLIENT_ID))
+        new ServiceBuilder(cfg.getString(InitOAuth.CLIENT_ID))
             .apiSecret(cfg.getString(InitOAuth.CLIENT_SECRET))
-            .scope("openid profile email offline_access")
+            .defaultScope("openid profile email offline_access")
             .callback(canonicalWebUrl + "oauth")
-            .build();
+            .build(new DexApi(rootUrl));
   }
 
   private String parseJwt(String input) {
@@ -120,14 +121,20 @@
 
   @Override
   public OAuthToken getAccessToken(OAuthVerifier rv) {
-    Verifier vi = new Verifier(rv.getValue());
-    Token to = service.getAccessToken(null, vi);
-    return new OAuthToken(to.getToken(), to.getSecret(), to.getRawResponse());
+    try {
+      OAuth2AccessToken accessToken = service.getAccessToken(rv.getValue());
+      return new OAuthToken(
+          accessToken.getAccessToken(), accessToken.getTokenType(), accessToken.getRawResponse());
+    } catch (InterruptedException | ExecutionException | IOException e) {
+      String msg = "Cannot retrieve access token";
+      log.error(msg, e);
+      throw new RuntimeException(msg, e);
+    }
   }
 
   @Override
   public String getAuthorizationUrl() {
-    return service.getAuthorizationUrl(null);
+    return service.getAuthorizationUrl();
   }
 
   @Override
diff --git a/src/main/java/com/googlesource/gerrit/plugins/oauth/Facebook2Api.java b/src/main/java/com/googlesource/gerrit/plugins/oauth/Facebook2Api.java
index a547bfb..96f4636 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/oauth/Facebook2Api.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/oauth/Facebook2Api.java
@@ -14,12 +14,23 @@
 
 package com.googlesource.gerrit.plugins.oauth;
 
-import org.scribe.builder.api.FacebookApi;
-import org.scribe.extractors.AccessTokenExtractor;
+import com.github.scribejava.core.builder.api.DefaultApi20;
+import com.github.scribejava.core.oauth2.clientauthentication.ClientAuthentication;
+import com.github.scribejava.core.oauth2.clientauthentication.RequestBodyAuthenticationScheme;
 
-public class Facebook2Api extends FacebookApi {
+public class Facebook2Api extends DefaultApi20 {
   @Override
-  public AccessTokenExtractor getAccessTokenExtractor() {
-    return OAuth2AccessTokenJsonExtractor.instance();
+  protected String getAuthorizationBaseUrl() {
+    return "https://www.facebook.com/dialog/oauth";
+  }
+
+  @Override
+  public String getAccessTokenEndpoint() {
+    return "https://graph.facebook.com/oauth/access_token";
+  }
+
+  @Override
+  public ClientAuthentication getClientAuthentication() {
+    return RequestBodyAuthenticationScheme.instance();
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/oauth/FacebookOAuthService.java b/src/main/java/com/googlesource/gerrit/plugins/oauth/FacebookOAuthService.java
index 7461154..66a063e 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/oauth/FacebookOAuthService.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/oauth/FacebookOAuthService.java
@@ -14,13 +14,20 @@
 
 package com.googlesource.gerrit.plugins.oauth;
 
+import static com.google.gerrit.server.OutputFormat.JSON;
+
+import com.github.scribejava.core.builder.ServiceBuilder;
+import com.github.scribejava.core.model.OAuth2AccessToken;
+import com.github.scribejava.core.model.OAuthRequest;
+import com.github.scribejava.core.model.Response;
+import com.github.scribejava.core.model.Verb;
+import com.github.scribejava.core.oauth.OAuth20Service;
 import com.google.common.base.CharMatcher;
 import com.google.gerrit.extensions.annotations.PluginName;
 import com.google.gerrit.extensions.auth.oauth.OAuthServiceProvider;
 import com.google.gerrit.extensions.auth.oauth.OAuthToken;
 import com.google.gerrit.extensions.auth.oauth.OAuthUserInfo;
 import com.google.gerrit.extensions.auth.oauth.OAuthVerifier;
-import com.google.gerrit.server.OutputFormat;
 import com.google.gerrit.server.config.CanonicalWebUrl;
 import com.google.gerrit.server.config.PluginConfig;
 import com.google.gerrit.server.config.PluginConfigFactory;
@@ -30,14 +37,8 @@
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
 import java.io.IOException;
+import java.util.concurrent.ExecutionException;
 import javax.servlet.http.HttpServletResponse;
-import org.scribe.builder.ServiceBuilder;
-import org.scribe.model.OAuthRequest;
-import org.scribe.model.Response;
-import org.scribe.model.Token;
-import org.scribe.model.Verb;
-import org.scribe.model.Verifier;
-import org.scribe.oauth.OAuthService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -51,7 +52,7 @@
   private static final String SCOPE = "email";
   private static final String FIELDS_QUERY = "fields";
   private static final String FIELDS = "email,name";
-  private final OAuthService service;
+  private final OAuth20Service service;
 
   @Inject
   FacebookOAuthService(
@@ -63,54 +64,55 @@
     String canonicalWebUrl = CharMatcher.is('/').trimTrailingFrom(urlProvider.get()) + "/";
 
     service =
-        new ServiceBuilder()
-            .provider(Facebook2Api.class)
-            .apiKey(cfg.getString(InitOAuth.CLIENT_ID))
+        new ServiceBuilder(cfg.getString(InitOAuth.CLIENT_ID))
             .apiSecret(cfg.getString(InitOAuth.CLIENT_SECRET))
             .callback(canonicalWebUrl + "oauth")
-            .scope(SCOPE)
-            .build();
+            .defaultScope(SCOPE)
+            .build(new Facebook2Api());
   }
 
   @Override
   public OAuthUserInfo getUserInfo(OAuthToken token) throws IOException {
     OAuthRequest request = new OAuthRequest(Verb.GET, PROTECTED_RESOURCE_URL);
-    Token t = new Token(token.getToken(), token.getSecret(), token.getRaw());
     request.addQuerystringParameter(FIELDS_QUERY, FIELDS);
+    OAuth2AccessToken t = new OAuth2AccessToken(token.getToken(), token.getRaw());
     service.signRequest(t, request);
-    Response response = request.send();
 
-    if (response.getCode() != HttpServletResponse.SC_OK) {
-      throw new IOException(
-          String.format(
-              "Status %s (%s) for request %s",
-              response.getCode(), response.getBody(), request.getUrl()));
-    }
-    JsonElement userJson =
-        OutputFormat.JSON.newGson().fromJson(response.getBody(), JsonElement.class);
-
-    if (log.isDebugEnabled()) {
-      log.debug("User info response: {}", response.getBody());
-    }
-    if (userJson.isJsonObject()) {
-      JsonObject jsonObject = userJson.getAsJsonObject();
-      JsonElement id = jsonObject.get("id");
-      if (id == null || id.isJsonNull()) {
-        throw new IOException("Response doesn't contain id field");
+    JsonElement userJson = null;
+    try (Response response = service.execute(request)) {
+      if (response.getCode() != HttpServletResponse.SC_OK) {
+        throw new IOException(
+            String.format(
+                "Status %s (%s) for request %s",
+                response.getCode(), response.getBody(), request.getUrl()));
       }
-      JsonElement email = jsonObject.get("email");
-      JsonElement name = jsonObject.get("name");
-      // Heads up!
-      // Lets keep `login` equal to `email`, since `username` field is
-      // deprecated for Facebook API versions v2.0 and higher
-      JsonElement login = jsonObject.get("email");
+      userJson = JSON.newGson().fromJson(response.getBody(), JsonElement.class);
 
-      return new OAuthUserInfo(
-          FACEBOOK_PROVIDER_PREFIX + id.getAsString(),
-          login == null || login.isJsonNull() ? null : login.getAsString(),
-          email == null || email.isJsonNull() ? null : email.getAsString(),
-          name == null || name.isJsonNull() ? null : name.getAsString(),
-          null);
+      if (log.isDebugEnabled()) {
+        log.debug("User info response: {}", response.getBody());
+      }
+      if (userJson.isJsonObject()) {
+        JsonObject jsonObject = userJson.getAsJsonObject();
+        JsonElement id = jsonObject.get("id");
+        if (id == null || id.isJsonNull()) {
+          throw new IOException("Response doesn't contain id field");
+        }
+        JsonElement email = jsonObject.get("email");
+        JsonElement name = jsonObject.get("name");
+        // Heads up!
+        // Lets keep `login` equal to `email`, since `username` field is
+        // deprecated for Facebook API versions v2.0 and higher
+        JsonElement login = jsonObject.get("email");
+
+        return new OAuthUserInfo(
+            FACEBOOK_PROVIDER_PREFIX + id.getAsString(),
+            login == null || login.isJsonNull() ? null : login.getAsString(),
+            email == null || email.isJsonNull() ? null : email.getAsString(),
+            name == null || name.isJsonNull() ? null : name.getAsString(),
+            null);
+      }
+    } catch (ExecutionException | InterruptedException e) {
+      throw new RuntimeException("Cannot retrieve user info resource", e);
     }
 
     throw new IOException(String.format("Invalid JSON '%s': not a JSON Object", userJson));
@@ -118,16 +120,20 @@
 
   @Override
   public OAuthToken getAccessToken(OAuthVerifier rv) {
-    Verifier vi = new Verifier(rv.getValue());
-    Token to = service.getAccessToken(null, vi);
-    OAuthToken result = new OAuthToken(to.getToken(), to.getSecret(), to.getRawResponse());
-
-    return result;
+    try {
+      OAuth2AccessToken accessToken = service.getAccessToken(rv.getValue());
+      return new OAuthToken(
+          accessToken.getAccessToken(), accessToken.getTokenType(), accessToken.getRawResponse());
+    } catch (InterruptedException | ExecutionException | IOException e) {
+      String msg = "Cannot retrieve access token";
+      log.error(msg, e);
+      throw new RuntimeException(msg, e);
+    }
   }
 
   @Override
   public String getAuthorizationUrl() {
-    return service.getAuthorizationUrl(null);
+    return service.getAuthorizationUrl();
   }
 
   @Override
diff --git a/src/main/java/com/googlesource/gerrit/plugins/oauth/GitHub2Api.java b/src/main/java/com/googlesource/gerrit/plugins/oauth/GitHub2Api.java
index 338e3cc..477eadf 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/oauth/GitHub2Api.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/oauth/GitHub2Api.java
@@ -14,13 +14,13 @@
 
 package com.googlesource.gerrit.plugins.oauth;
 
-import org.scribe.builder.api.DefaultApi20;
-import org.scribe.model.OAuthConfig;
-import org.scribe.utils.OAuthEncoder;
+import com.github.scribejava.core.builder.api.DefaultApi20;
+import com.github.scribejava.core.extractors.OAuth2AccessTokenExtractor;
+import com.github.scribejava.core.extractors.TokenExtractor;
+import com.github.scribejava.core.model.OAuth2AccessToken;
 
 public class GitHub2Api extends DefaultApi20 {
-  private static final String AUTHORIZE_URL =
-      "%slogin/oauth/authorize?client_id=%s&redirect_uri=%s";
+  private static final String AUTHORIZE_URL = "%slogin/oauth/authorize";
 
   private final String rootUrl;
 
@@ -34,8 +34,12 @@
   }
 
   @Override
-  public String getAuthorizationUrl(OAuthConfig config) {
-    return String.format(
-        AUTHORIZE_URL, rootUrl, config.getApiKey(), OAuthEncoder.encode(config.getCallback()));
+  protected String getAuthorizationBaseUrl() {
+    return String.format(AUTHORIZE_URL, rootUrl);
+  }
+
+  @Override
+  public TokenExtractor<OAuth2AccessToken> getAccessTokenExtractor() {
+    return OAuth2AccessTokenExtractor.instance();
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/oauth/GitHubOAuthService.java b/src/main/java/com/googlesource/gerrit/plugins/oauth/GitHubOAuthService.java
index 0122b1c..c22e95d 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/oauth/GitHubOAuthService.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/oauth/GitHubOAuthService.java
@@ -14,13 +14,20 @@
 
 package com.googlesource.gerrit.plugins.oauth;
 
+import static com.google.gerrit.server.OutputFormat.JSON;
+
+import com.github.scribejava.core.builder.ServiceBuilder;
+import com.github.scribejava.core.model.OAuth2AccessToken;
+import com.github.scribejava.core.model.OAuthRequest;
+import com.github.scribejava.core.model.Response;
+import com.github.scribejava.core.model.Verb;
+import com.github.scribejava.core.oauth.OAuth20Service;
 import com.google.common.base.CharMatcher;
 import com.google.gerrit.extensions.annotations.PluginName;
 import com.google.gerrit.extensions.auth.oauth.OAuthServiceProvider;
 import com.google.gerrit.extensions.auth.oauth.OAuthToken;
 import com.google.gerrit.extensions.auth.oauth.OAuthUserInfo;
 import com.google.gerrit.extensions.auth.oauth.OAuthVerifier;
-import com.google.gerrit.server.OutputFormat;
 import com.google.gerrit.server.config.CanonicalWebUrl;
 import com.google.gerrit.server.config.PluginConfig;
 import com.google.gerrit.server.config.PluginConfigFactory;
@@ -30,14 +37,8 @@
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
 import java.io.IOException;
+import java.util.concurrent.ExecutionException;
 import javax.servlet.http.HttpServletResponse;
-import org.scribe.builder.ServiceBuilder;
-import org.scribe.model.OAuthRequest;
-import org.scribe.model.Response;
-import org.scribe.model.Token;
-import org.scribe.model.Verb;
-import org.scribe.model.Verifier;
-import org.scribe.oauth.OAuthService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -51,9 +52,9 @@
   static final String GITHUB_ROOT_URL = "https://github.com/";
   private final String rootUrl;
 
-  private static final String SCOPE = "user:email";
+  static final String SCOPE = "user:email";
   private final boolean fixLegacyUserId;
-  private final OAuthService service;
+  private final OAuth20Service service;
 
   @Inject
   GitHubOAuthService(
@@ -68,13 +69,11 @@
             + "/";
 
     service =
-        new ServiceBuilder()
-            .provider(new GitHub2Api(rootUrl))
-            .apiKey(cfg.getString(InitOAuth.CLIENT_ID))
+        new ServiceBuilder(cfg.getString(InitOAuth.CLIENT_ID))
             .apiSecret(cfg.getString(InitOAuth.CLIENT_SECRET))
             .callback(canonicalWebUrl + "oauth")
-            .scope(SCOPE)
-            .build();
+            .defaultScope(SCOPE)
+            .build(new GitHub2Api(rootUrl));
   }
 
   private String getApiUrl() {
@@ -90,35 +89,39 @@
   @Override
   public OAuthUserInfo getUserInfo(OAuthToken token) throws IOException {
     OAuthRequest request = new OAuthRequest(Verb.GET, getProtectedResourceUrl());
-    Token t = new Token(token.getToken(), token.getSecret(), token.getRaw());
+    OAuth2AccessToken t = new OAuth2AccessToken(token.getToken(), token.getRaw());
     service.signRequest(t, request);
-    Response response = request.send();
-    if (response.getCode() != HttpServletResponse.SC_OK) {
-      throw new IOException(
-          String.format(
-              "Status %s (%s) for request %s",
-              response.getCode(), response.getBody(), request.getUrl()));
-    }
-    JsonElement userJson =
-        OutputFormat.JSON.newGson().fromJson(response.getBody(), JsonElement.class);
-    if (log.isDebugEnabled()) {
-      log.debug("User info response: {}", response.getBody());
-    }
-    if (userJson.isJsonObject()) {
-      JsonObject jsonObject = userJson.getAsJsonObject();
-      JsonElement id = jsonObject.get("id");
-      if (id == null || id.isJsonNull()) {
-        throw new IOException("Response doesn't contain id field");
+
+    JsonElement userJson = null;
+    try (Response response = service.execute(request)) {
+      if (response.getCode() != HttpServletResponse.SC_OK) {
+        throw new IOException(
+            String.format(
+                "Status %s (%s) for request %s",
+                response.getCode(), response.getBody(), request.getUrl()));
       }
-      JsonElement email = jsonObject.get("email");
-      JsonElement name = jsonObject.get("name");
-      JsonElement login = jsonObject.get("login");
-      return new OAuthUserInfo(
-          GITHUB_PROVIDER_PREFIX + id.getAsString(),
-          login == null || login.isJsonNull() ? null : login.getAsString(),
-          email == null || email.isJsonNull() ? null : email.getAsString(),
-          name == null || name.isJsonNull() ? null : name.getAsString(),
-          fixLegacyUserId ? id.getAsString() : null);
+      userJson = JSON.newGson().fromJson(response.getBody(), JsonElement.class);
+      if (log.isDebugEnabled()) {
+        log.debug("User info response: {}", response.getBody());
+      }
+      if (userJson.isJsonObject()) {
+        JsonObject jsonObject = userJson.getAsJsonObject();
+        JsonElement id = jsonObject.get("id");
+        if (id == null || id.isJsonNull()) {
+          throw new IOException("Response doesn't contain id field");
+        }
+        JsonElement email = jsonObject.get("email");
+        JsonElement name = jsonObject.get("name");
+        JsonElement login = jsonObject.get("login");
+        return new OAuthUserInfo(
+            GITHUB_PROVIDER_PREFIX + id.getAsString(),
+            login == null || login.isJsonNull() ? null : login.getAsString(),
+            email == null || email.isJsonNull() ? null : email.getAsString(),
+            name == null || name.isJsonNull() ? null : name.getAsString(),
+            fixLegacyUserId ? id.getAsString() : null);
+      }
+    } catch (ExecutionException | InterruptedException e) {
+      throw new RuntimeException("Cannot retrieve user info resource", e);
     }
 
     throw new IOException(String.format("Invalid JSON '%s': not a JSON Object", userJson));
@@ -126,15 +129,20 @@
 
   @Override
   public OAuthToken getAccessToken(OAuthVerifier rv) {
-    Verifier vi = new Verifier(rv.getValue());
-    Token to = service.getAccessToken(null, vi);
-    OAuthToken result = new OAuthToken(to.getToken(), to.getSecret(), to.getRawResponse());
-    return result;
+    try {
+      OAuth2AccessToken accessToken = service.getAccessToken(rv.getValue());
+      return new OAuthToken(
+          accessToken.getAccessToken(), accessToken.getTokenType(), accessToken.getRawResponse());
+    } catch (InterruptedException | ExecutionException | IOException e) {
+      String msg = "Cannot retrieve access token";
+      log.error(msg, e);
+      throw new RuntimeException(msg, e);
+    }
   }
 
   @Override
   public String getAuthorizationUrl() {
-    return service.getAuthorizationUrl(null);
+    return service.getAuthorizationUrl();
   }
 
   @Override
diff --git a/src/main/java/com/googlesource/gerrit/plugins/oauth/GitLabApi.java b/src/main/java/com/googlesource/gerrit/plugins/oauth/GitLabApi.java
index db0851f..ca88d05 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/oauth/GitLabApi.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/oauth/GitLabApi.java
@@ -14,15 +14,12 @@
 
 package com.googlesource.gerrit.plugins.oauth;
 
-import org.scribe.builder.api.DefaultApi20;
-import org.scribe.extractors.AccessTokenExtractor;
-import org.scribe.model.OAuthConfig;
-import org.scribe.model.Verb;
-import org.scribe.oauth.OAuthService;
+import com.github.scribejava.core.builder.api.DefaultApi20;
+import com.github.scribejava.core.oauth2.clientauthentication.ClientAuthentication;
+import com.github.scribejava.core.oauth2.clientauthentication.RequestBodyAuthenticationScheme;
 
 public class GitLabApi extends DefaultApi20 {
-  private static final String AUTHORIZE_URL =
-      "%s/oauth/authorize?client_id=%s&response_type=code&redirect_uri=%s";
+  private static final String AUTHORIZE_URL = "%s/oauth/authorize";
 
   private final String rootUrl;
 
@@ -31,8 +28,8 @@
   }
 
   @Override
-  public String getAuthorizationUrl(OAuthConfig config) {
-    return String.format(AUTHORIZE_URL, rootUrl, config.getApiKey(), config.getCallback());
+  public String getAuthorizationBaseUrl() {
+    return String.format(AUTHORIZE_URL, rootUrl);
   }
 
   @Override
@@ -41,17 +38,7 @@
   }
 
   @Override
-  public Verb getAccessTokenVerb() {
-    return Verb.POST;
-  }
-
-  @Override
-  public OAuthService createService(OAuthConfig config) {
-    return new OAuth20ServiceImpl(this, config);
-  }
-
-  @Override
-  public AccessTokenExtractor getAccessTokenExtractor() {
-    return OAuth2AccessTokenJsonExtractor.instance();
+  public ClientAuthentication getClientAuthentication() {
+    return RequestBodyAuthenticationScheme.instance();
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/oauth/GitLabOAuthService.java b/src/main/java/com/googlesource/gerrit/plugins/oauth/GitLabOAuthService.java
index 7e67424..394c3e4 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/oauth/GitLabOAuthService.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/oauth/GitLabOAuthService.java
@@ -18,6 +18,12 @@
 import static javax.servlet.http.HttpServletResponse.SC_OK;
 import static org.slf4j.LoggerFactory.getLogger;
 
+import com.github.scribejava.core.builder.ServiceBuilder;
+import com.github.scribejava.core.model.OAuth2AccessToken;
+import com.github.scribejava.core.model.OAuthRequest;
+import com.github.scribejava.core.model.Response;
+import com.github.scribejava.core.model.Verb;
+import com.github.scribejava.core.oauth.OAuth20Service;
 import com.google.common.base.CharMatcher;
 import com.google.gerrit.extensions.annotations.PluginName;
 import com.google.gerrit.extensions.auth.oauth.OAuthServiceProvider;
@@ -35,13 +41,7 @@
 import com.google.inject.Singleton;
 import java.io.IOException;
 import java.net.URI;
-import org.scribe.builder.ServiceBuilder;
-import org.scribe.model.OAuthRequest;
-import org.scribe.model.Response;
-import org.scribe.model.Token;
-import org.scribe.model.Verb;
-import org.scribe.model.Verifier;
-import org.scribe.oauth.OAuthService;
+import java.util.concurrent.ExecutionException;
 import org.slf4j.Logger;
 
 @Singleton
@@ -50,7 +50,7 @@
   static final String CONFIG_SUFFIX = "-gitlab-oauth";
   private static final String PROTECTED_RESOURCE_URL = "%s/api/v3/user";
   private static final String GITLAB_PROVIDER_PREFIX = "gitlab-oauth:";
-  private final OAuthService service;
+  private final OAuth20Service service;
   private final String rootUrl;
 
   @Inject
@@ -65,58 +65,65 @@
       throw new ProvisionException("Root URL must be absolute URL");
     }
     service =
-        new ServiceBuilder()
-            .provider(new GitLabApi(rootUrl))
-            .apiKey(cfg.getString(InitOAuth.CLIENT_ID))
+        new ServiceBuilder(cfg.getString(InitOAuth.CLIENT_ID))
             .apiSecret(cfg.getString(InitOAuth.CLIENT_SECRET))
             .callback(canonicalWebUrl + "oauth")
-            .build();
+            .build(new GitLabApi(rootUrl));
   }
 
   @Override
   public OAuthUserInfo getUserInfo(OAuthToken token) throws IOException {
-    final String protectedResourceUrl = String.format(PROTECTED_RESOURCE_URL, rootUrl);
-    OAuthRequest request = new OAuthRequest(Verb.GET, protectedResourceUrl);
-    Token t = new Token(token.getToken(), token.getSecret(), token.getRaw());
+    OAuthRequest request =
+        new OAuthRequest(Verb.GET, String.format(PROTECTED_RESOURCE_URL, rootUrl));
+    OAuth2AccessToken t = new OAuth2AccessToken(token.getToken(), token.getRaw());
     service.signRequest(t, request);
 
-    Response response = request.send();
-    if (response.getCode() != SC_OK) {
-      throw new IOException(
-          String.format(
-              "Status %s (%s) for request %s",
-              response.getCode(), response.getBody(), request.getUrl()));
+    try (Response response = service.execute(request)) {
+      if (response.getCode() != SC_OK) {
+        throw new IOException(
+            String.format(
+                "Status %s (%s) for request %s",
+                response.getCode(), response.getBody(), request.getUrl()));
+      }
+      JsonElement userJson = JSON.newGson().fromJson(response.getBody(), JsonElement.class);
+      if (log.isDebugEnabled()) {
+        log.debug("User info response: {}", response.getBody());
+      }
+      JsonObject jsonObject = userJson.getAsJsonObject();
+      if (jsonObject == null || jsonObject.isJsonNull()) {
+        throw new IOException("Response doesn't contain 'user' field" + jsonObject);
+      }
+      JsonElement id = jsonObject.get("id");
+      JsonElement username = jsonObject.get("username");
+      JsonElement email = jsonObject.get("email");
+      JsonElement name = jsonObject.get("name");
+      return new OAuthUserInfo(
+          GITLAB_PROVIDER_PREFIX + id.getAsString(),
+          username == null || username.isJsonNull() ? null : username.getAsString(),
+          email == null || email.isJsonNull() ? null : email.getAsString(),
+          name == null || name.isJsonNull() ? null : name.getAsString(),
+          null);
+    } catch (ExecutionException | InterruptedException e) {
+      throw new RuntimeException("Cannot retrieve user info resource", e);
     }
-    JsonElement userJson = JSON.newGson().fromJson(response.getBody(), JsonElement.class);
-    if (log.isDebugEnabled()) {
-      log.debug("User info response: {}", response.getBody());
-    }
-    JsonObject jsonObject = userJson.getAsJsonObject();
-    if (jsonObject == null || jsonObject.isJsonNull()) {
-      throw new IOException("Response doesn't contain 'user' field" + jsonObject);
-    }
-    JsonElement id = jsonObject.get("id");
-    JsonElement username = jsonObject.get("username");
-    JsonElement email = jsonObject.get("email");
-    JsonElement name = jsonObject.get("name");
-    return new OAuthUserInfo(
-        GITLAB_PROVIDER_PREFIX + id.getAsString(),
-        username == null || username.isJsonNull() ? null : username.getAsString(),
-        email == null || email.isJsonNull() ? null : email.getAsString(),
-        name == null || name.isJsonNull() ? null : name.getAsString(),
-        null);
   }
 
   @Override
   public OAuthToken getAccessToken(OAuthVerifier rv) {
-    Verifier vi = new Verifier(rv.getValue());
-    Token to = service.getAccessToken(null, vi);
-    return new OAuthToken(to.getToken(), to.getSecret(), to.getRawResponse());
+    try {
+      OAuth2AccessToken accessToken = service.getAccessToken(rv.getValue());
+      return new OAuthToken(
+          accessToken.getAccessToken(), accessToken.getTokenType(), accessToken.getRawResponse());
+    } catch (InterruptedException | ExecutionException | IOException e) {
+      String msg = "Cannot retrieve access token";
+      log.error(msg, e);
+      throw new RuntimeException(msg, e);
+    }
   }
 
   @Override
   public String getAuthorizationUrl() {
-    return service.getAuthorizationUrl(null);
+    return service.getAuthorizationUrl();
   }
 
   @Override
diff --git a/src/main/java/com/googlesource/gerrit/plugins/oauth/Google2Api.java b/src/main/java/com/googlesource/gerrit/plugins/oauth/Google2Api.java
index 88c640d..4f2eed2 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/oauth/Google2Api.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/oauth/Google2Api.java
@@ -14,50 +14,16 @@
 
 package com.googlesource.gerrit.plugins.oauth;
 
-import static org.scribe.utils.OAuthEncoder.encode;
+import com.github.scribejava.core.builder.api.DefaultApi20;
 
-import org.scribe.builder.api.DefaultApi20;
-import org.scribe.extractors.AccessTokenExtractor;
-import org.scribe.model.OAuthConfig;
-import org.scribe.model.Verb;
-import org.scribe.oauth.OAuthService;
-import org.scribe.utils.Preconditions;
-
-// Source: https://github.com/FeedTheCoffers/scribe-java-extras
-// License: Apache 2
-// https://github.com/FeedTheCoffers/scribe-java-extras/blob/master/pom.xml
 public class Google2Api extends DefaultApi20 {
-  private static final String AUTHORIZE_URL =
-      "https://accounts.google.com/o/oauth2/auth?response_type=code&client_id=%s&redirect_uri=%s&scope=%s";
-
   @Override
   public String getAccessTokenEndpoint() {
-    return "https://accounts.google.com/o/oauth2/token";
+    return "https://www.googleapis.com/oauth2/v4/token";
   }
 
   @Override
-  public String getAuthorizationUrl(OAuthConfig config) {
-    Preconditions.checkValidUrl(
-        config.getCallback(), "Must provide a valid url as callback. Google does not support OOB");
-    Preconditions.checkEmptyString(
-        config.getScope(), "Must provide a valid value as scope. Google does not support no scope");
-
-    return String.format(
-        AUTHORIZE_URL, config.getApiKey(), encode(config.getCallback()), encode(config.getScope()));
-  }
-
-  @Override
-  public Verb getAccessTokenVerb() {
-    return Verb.POST;
-  }
-
-  @Override
-  public OAuthService createService(OAuthConfig config) {
-    return new OAuth20ServiceImpl(this, config);
-  }
-
-  @Override
-  public AccessTokenExtractor getAccessTokenExtractor() {
-    return OAuth2AccessTokenJsonExtractor.instance();
+  public String getAuthorizationBaseUrl() {
+    return "https://accounts.google.com/o/oauth2/auth";
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/oauth/GoogleOAuthService.java b/src/main/java/com/googlesource/gerrit/plugins/oauth/GoogleOAuthService.java
index 1199ea3..fdfd05f 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/oauth/GoogleOAuthService.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/oauth/GoogleOAuthService.java
@@ -14,6 +14,14 @@
 
 package com.googlesource.gerrit.plugins.oauth;
 
+import static com.google.gerrit.server.OutputFormat.JSON;
+
+import com.github.scribejava.core.builder.ServiceBuilder;
+import com.github.scribejava.core.model.OAuth2AccessToken;
+import com.github.scribejava.core.model.OAuthRequest;
+import com.github.scribejava.core.model.Response;
+import com.github.scribejava.core.model.Verb;
+import com.github.scribejava.core.oauth.OAuth20Service;
 import com.google.common.base.CharMatcher;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Strings;
@@ -37,15 +45,9 @@
 import java.nio.charset.StandardCharsets;
 import java.util.Arrays;
 import java.util.List;
+import java.util.concurrent.ExecutionException;
 import javax.servlet.http.HttpServletResponse;
 import org.apache.commons.codec.binary.Base64;
-import org.scribe.builder.ServiceBuilder;
-import org.scribe.model.OAuthRequest;
-import org.scribe.model.Response;
-import org.scribe.model.Token;
-import org.scribe.model.Verb;
-import org.scribe.model.Verifier;
-import org.scribe.oauth.OAuthService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -57,7 +59,7 @@
   private static final String PROTECTED_RESOURCE_URL =
       "https://www.googleapis.com/oauth2/v2/userinfo";
   private static final String SCOPE = "email profile";
-  private final OAuthService service;
+  private final OAuth20Service service;
   private final String canonicalWebUrl;
   private final List<String> domains;
   private final boolean useEmailAsUsername;
@@ -79,13 +81,11 @@
     this.domains = Arrays.asList(cfg.getStringList(InitOAuth.DOMAIN));
     this.useEmailAsUsername = cfg.getBoolean(InitOAuth.USE_EMAIL_AS_USERNAME, false);
     this.service =
-        new ServiceBuilder()
-            .provider(Google2Api.class)
-            .apiKey(cfg.getString(InitOAuth.CLIENT_ID))
+        new ServiceBuilder(cfg.getString(InitOAuth.CLIENT_ID))
             .apiSecret(cfg.getString(InitOAuth.CLIENT_SECRET))
             .callback(canonicalWebUrl + "oauth")
-            .scope(SCOPE)
-            .build();
+            .defaultScope(SCOPE)
+            .build(new Google2Api());
     if (log.isDebugEnabled()) {
       log.debug("OAuth2: canonicalWebUrl={}", canonicalWebUrl);
       log.debug("OAuth2: scope={}", SCOPE);
@@ -97,55 +97,59 @@
   @Override
   public OAuthUserInfo getUserInfo(OAuthToken token) throws IOException {
     OAuthRequest request = new OAuthRequest(Verb.GET, PROTECTED_RESOURCE_URL);
-    Token t = new Token(token.getToken(), token.getSecret(), token.getRaw());
+    OAuth2AccessToken t = new OAuth2AccessToken(token.getToken(), token.getRaw());
     service.signRequest(t, request);
-    Response response = request.send();
-    if (response.getCode() != HttpServletResponse.SC_OK) {
-      throw new IOException(
-          String.format(
-              "Status %s (%s) for request %s",
-              response.getCode(), response.getBody(), request.getUrl()));
-    }
-    JsonElement userJson =
-        OutputFormat.JSON.newGson().fromJson(response.getBody(), JsonElement.class);
-    if (log.isDebugEnabled()) {
-      log.debug("User info response: {}", response.getBody());
-    }
-    if (userJson.isJsonObject()) {
-      JsonObject jsonObject = userJson.getAsJsonObject();
-      JsonElement id = jsonObject.get("id");
-      if (id == null || id.isJsonNull()) {
-        throw new IOException("Response doesn't contain id field");
-      }
-      JsonElement email = jsonObject.get("email");
-      JsonElement name = jsonObject.get("name");
-      String login = null;
 
-      if (domains.size() > 0) {
-        boolean domainMatched = false;
-        JsonObject jwtToken = retrieveJWTToken(token);
-        String hdClaim = retrieveHostedDomain(jwtToken);
-        for (String domain : domains) {
-          if (domain.equalsIgnoreCase(hdClaim)) {
-            domainMatched = true;
-            break;
+    JsonElement userJson = null;
+    try (Response response = service.execute(request)) {
+      if (response.getCode() != HttpServletResponse.SC_OK) {
+        throw new IOException(
+            String.format(
+                "Status %s (%s) for request %s",
+                response.getCode(), response.getBody(), request.getUrl()));
+      }
+      userJson = JSON.newGson().fromJson(response.getBody(), JsonElement.class);
+      if (log.isDebugEnabled()) {
+        log.debug("User info response: {}", response.getBody());
+      }
+      if (userJson.isJsonObject()) {
+        JsonObject jsonObject = userJson.getAsJsonObject();
+        JsonElement id = jsonObject.get("id");
+        if (id == null || id.isJsonNull()) {
+          throw new IOException("Response doesn't contain id field");
+        }
+        JsonElement email = jsonObject.get("email");
+        JsonElement name = jsonObject.get("name");
+        String login = null;
+
+        if (domains.size() > 0) {
+          boolean domainMatched = false;
+          JsonObject jwtToken = retrieveJWTToken(token);
+          String hdClaim = retrieveHostedDomain(jwtToken);
+          for (String domain : domains) {
+            if (domain.equalsIgnoreCase(hdClaim)) {
+              domainMatched = true;
+              break;
+            }
+          }
+          if (!domainMatched) {
+            // TODO(davido): improve error reporting in OAuth extension point
+            log.error("Error: hosted domain validation failed: {}", Strings.nullToEmpty(hdClaim));
+            return null;
           }
         }
-        if (!domainMatched) {
-          // TODO(davido): improve error reporting in OAuth extension point
-          log.error("Error: hosted domain validation failed: {}", Strings.nullToEmpty(hdClaim));
-          return null;
+        if (useEmailAsUsername && !email.isJsonNull()) {
+          login = email.getAsString().split("@")[0];
         }
+        return new OAuthUserInfo(
+            GOOGLE_PROVIDER_PREFIX + id.getAsString() /*externalId*/,
+            login /*username*/,
+            email == null || email.isJsonNull() ? null : email.getAsString() /*email*/,
+            name == null || name.isJsonNull() ? null : name.getAsString() /*displayName*/,
+            fixLegacyUserId ? id.getAsString() : null /*claimedIdentity*/);
       }
-      if (useEmailAsUsername && !email.isJsonNull()) {
-        login = email.getAsString().split("@")[0];
-      }
-      return new OAuthUserInfo(
-          GOOGLE_PROVIDER_PREFIX + id.getAsString() /*externalId*/,
-          login /*username*/,
-          email == null || email.isJsonNull() ? null : email.getAsString() /*email*/,
-          name == null || name.isJsonNull() ? null : name.getAsString() /*displayName*/,
-          fixLegacyUserId ? id.getAsString() : null /*claimedIdentity*/);
+    } catch (ExecutionException | InterruptedException e) {
+      throw new RuntimeException("Cannot retrieve user info resource", e);
     }
 
     throw new IOException(String.format("Invalid JSON '%s': not a JSON Object", userJson));
@@ -198,15 +202,20 @@
 
   @Override
   public OAuthToken getAccessToken(OAuthVerifier rv) {
-    Verifier vi = new Verifier(rv.getValue());
-    Token to = service.getAccessToken(null, vi);
-    OAuthToken result = new OAuthToken(to.getToken(), to.getSecret(), to.getRawResponse());
-    return result;
+    try {
+      OAuth2AccessToken accessToken = service.getAccessToken(rv.getValue());
+      return new OAuthToken(
+          accessToken.getAccessToken(), accessToken.getTokenType(), accessToken.getRawResponse());
+    } catch (InterruptedException | ExecutionException | IOException e) {
+      String msg = "Cannot retrieve access token";
+      log.error(msg, e);
+      throw new RuntimeException(msg, e);
+    }
   }
 
   @Override
   public String getAuthorizationUrl() {
-    String url = service.getAuthorizationUrl(null);
+    String url = service.getAuthorizationUrl();
     try {
       if (domains.size() == 1) {
         url += "&hd=" + URLEncoder.encode(domains.get(0), StandardCharsets.UTF_8.name());
diff --git a/src/main/java/com/googlesource/gerrit/plugins/oauth/KeycloakApi.java b/src/main/java/com/googlesource/gerrit/plugins/oauth/KeycloakApi.java
index 581d562..10482da 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/oauth/KeycloakApi.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/oauth/KeycloakApi.java
@@ -14,18 +14,18 @@
 
 package com.googlesource.gerrit.plugins.oauth;
 
-import org.scribe.builder.api.DefaultApi20;
-import org.scribe.extractors.AccessTokenExtractor;
-import org.scribe.extractors.JsonTokenExtractor;
-import org.scribe.model.OAuthConfig;
-import org.scribe.model.Verb;
-import org.scribe.oauth.OAuthService;
-import org.scribe.utils.OAuthEncoder;
+import com.github.scribejava.core.builder.api.DefaultApi20;
+import com.github.scribejava.core.extractors.OAuth2AccessTokenExtractor;
+import com.github.scribejava.core.extractors.TokenExtractor;
+import com.github.scribejava.core.model.OAuth2AccessToken;
+import com.github.scribejava.core.oauth2.bearersignature.BearerSignature;
+import com.github.scribejava.core.oauth2.bearersignature.BearerSignatureURIQueryParameter;
+import com.github.scribejava.core.oauth2.clientauthentication.ClientAuthentication;
+import com.github.scribejava.core.oauth2.clientauthentication.RequestBodyAuthenticationScheme;
 
 public class KeycloakApi extends DefaultApi20 {
 
-  private static final String AUTHORIZE_URL =
-      "%s/auth/realms/%s/protocol/openid-connect/auth?client_id=%s&response_type=code&redirect_uri=%s&scope=%s";
+  private static final String AUTHORIZE_URL = "%s/auth/realms/%s/protocol/openid-connect/auth";
 
   private final String rootUrl;
   private final String realm;
@@ -36,14 +36,8 @@
   }
 
   @Override
-  public String getAuthorizationUrl(OAuthConfig config) {
-    return String.format(
-        AUTHORIZE_URL,
-        rootUrl,
-        realm,
-        config.getApiKey(),
-        OAuthEncoder.encode(config.getCallback()),
-        config.getScope().replaceAll(" ", "+"));
+  public String getAuthorizationBaseUrl() {
+    return String.format(AUTHORIZE_URL, rootUrl, realm);
   }
 
   @Override
@@ -52,17 +46,17 @@
   }
 
   @Override
-  public Verb getAccessTokenVerb() {
-    return Verb.POST;
+  public TokenExtractor<OAuth2AccessToken> getAccessTokenExtractor() {
+    return OAuth2AccessTokenExtractor.instance();
   }
 
   @Override
-  public OAuthService createService(OAuthConfig config) {
-    return new OAuth20ServiceImpl(this, config);
+  public BearerSignature getBearerSignature() {
+    return BearerSignatureURIQueryParameter.instance();
   }
 
   @Override
-  public AccessTokenExtractor getAccessTokenExtractor() {
-    return new JsonTokenExtractor();
+  public ClientAuthentication getClientAuthentication() {
+    return RequestBodyAuthenticationScheme.instance();
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/oauth/KeycloakOAuthService.java b/src/main/java/com/googlesource/gerrit/plugins/oauth/KeycloakOAuthService.java
index 99be7ee..937e1fd 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/oauth/KeycloakOAuthService.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/oauth/KeycloakOAuthService.java
@@ -16,6 +16,9 @@
 
 import static com.google.gerrit.server.OutputFormat.JSON;
 
+import com.github.scribejava.core.builder.ServiceBuilder;
+import com.github.scribejava.core.model.OAuth2AccessToken;
+import com.github.scribejava.core.oauth.OAuth20Service;
 import com.google.common.base.CharMatcher;
 import com.google.common.base.Preconditions;
 import com.google.gerrit.extensions.annotations.PluginName;
@@ -33,11 +36,8 @@
 import com.google.inject.ProvisionException;
 import java.io.IOException;
 import java.net.URI;
+import java.util.concurrent.ExecutionException;
 import org.apache.commons.codec.binary.Base64;
-import org.scribe.builder.ServiceBuilder;
-import org.scribe.model.Token;
-import org.scribe.model.Verifier;
-import org.scribe.oauth.OAuthService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -47,7 +47,7 @@
 
   static final String CONFIG_SUFFIX = "-keycloak-oauth";
   private static final String KEYCLOAK_PROVIDER_PREFIX = "keycloak-oauth:";
-  private final OAuthService service;
+  private final OAuth20Service service;
   private final String serviceName;
 
   @Inject
@@ -66,13 +66,11 @@
     serviceName = cfg.getString(InitOAuth.SERVICE_NAME, "Keycloak OAuth2");
 
     service =
-        new ServiceBuilder()
-            .provider(new KeycloakApi(rootUrl, realm))
-            .apiKey(cfg.getString(InitOAuth.CLIENT_ID))
+        new ServiceBuilder(cfg.getString(InitOAuth.CLIENT_ID))
             .apiSecret(cfg.getString(InitOAuth.CLIENT_SECRET))
-            .scope("openid")
             .callback(canonicalWebUrl + "oauth")
-            .build();
+            .defaultScope("openid")
+            .build(new KeycloakApi(rootUrl, realm));
   }
 
   private String parseJwt(String input) {
@@ -121,14 +119,20 @@
 
   @Override
   public OAuthToken getAccessToken(OAuthVerifier rv) {
-    Verifier vi = new Verifier(rv.getValue());
-    Token to = service.getAccessToken(null, vi);
-    return new OAuthToken(to.getToken(), to.getSecret(), to.getRawResponse());
+    try {
+      OAuth2AccessToken accessToken = service.getAccessToken(rv.getValue());
+      return new OAuthToken(
+          accessToken.getAccessToken(), accessToken.getTokenType(), accessToken.getRawResponse());
+    } catch (InterruptedException | ExecutionException | IOException e) {
+      String msg = "Cannot retrieve access token";
+      log.error(msg, e);
+      throw new RuntimeException(msg, e);
+    }
   }
 
   @Override
   public String getAuthorizationUrl() {
-    return service.getAuthorizationUrl(null);
+    return service.getAuthorizationUrl();
   }
 
   @Override
diff --git a/src/main/java/com/googlesource/gerrit/plugins/oauth/OAuth20ServiceImpl.java b/src/main/java/com/googlesource/gerrit/plugins/oauth/OAuth20ServiceImpl.java
deleted file mode 100644
index ecd8f26..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/oauth/OAuth20ServiceImpl.java
+++ /dev/null
@@ -1,93 +0,0 @@
-// Copyright (C) 2017 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.googlesource.gerrit.plugins.oauth;
-
-import static org.slf4j.LoggerFactory.getLogger;
-
-import org.scribe.builder.api.DefaultApi20;
-import org.scribe.model.OAuthConfig;
-import org.scribe.model.OAuthConstants;
-import org.scribe.model.OAuthRequest;
-import org.scribe.model.Response;
-import org.scribe.model.Token;
-import org.scribe.model.Verifier;
-import org.scribe.oauth.OAuthService;
-import org.slf4j.Logger;
-
-/** TODO(gildur): remove when updating to newer scribe lib */
-final class OAuth20ServiceImpl implements OAuthService {
-  private static final Logger log = getLogger(OAuth20ServiceImpl.class);
-
-  private static final String VERSION = "2.0";
-
-  private static final String GRANT_TYPE = "grant_type";
-  private static final String GRANT_TYPE_VALUE = "authorization_code";
-
-  private final DefaultApi20 api;
-  private final OAuthConfig config;
-
-  /**
-   * Default constructor
-   *
-   * @param api OAuth2.0 api information
-   * @param config OAuth 2.0 configuration param object
-   */
-  public OAuth20ServiceImpl(DefaultApi20 api, OAuthConfig config) {
-    this.api = api;
-    this.config = config;
-  }
-
-  @Override
-  public Token getAccessToken(Token requestToken, Verifier verifier) {
-    OAuthRequest request = new OAuthRequest(api.getAccessTokenVerb(), api.getAccessTokenEndpoint());
-    request.addBodyParameter(OAuthConstants.CLIENT_ID, config.getApiKey());
-    request.addBodyParameter(OAuthConstants.CLIENT_SECRET, config.getApiSecret());
-    request.addBodyParameter(OAuthConstants.CODE, verifier.getValue());
-    request.addBodyParameter(OAuthConstants.REDIRECT_URI, config.getCallback());
-    if (config.hasScope()) {
-      request.addBodyParameter(OAuthConstants.SCOPE, config.getScope());
-    }
-    request.addBodyParameter(GRANT_TYPE, GRANT_TYPE_VALUE);
-    if (log.isDebugEnabled()) {
-      log.debug("Access token request: {}", request);
-    }
-    Response response = request.send();
-    if (log.isDebugEnabled()) {
-      log.debug("Access token response: {}", response.getBody());
-    }
-    return api.getAccessTokenExtractor().extract(response.getBody());
-  }
-
-  @Override
-  public Token getRequestToken() {
-    throw new UnsupportedOperationException(
-        "Unsupported operation, please use 'getAuthorizationUrl' and redirect your users there");
-  }
-
-  @Override
-  public String getVersion() {
-    return VERSION;
-  }
-
-  @Override
-  public void signRequest(Token accessToken, OAuthRequest request) {
-    request.addQuerystringParameter(OAuthConstants.ACCESS_TOKEN, accessToken.getToken());
-  }
-
-  @Override
-  public String getAuthorizationUrl(Token requestToken) {
-    return api.getAuthorizationUrl(config);
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/oauth/OAuth2AccessTokenJsonExtractor.java b/src/main/java/com/googlesource/gerrit/plugins/oauth/OAuth2AccessTokenJsonExtractor.java
deleted file mode 100644
index 6c2f1a0..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/oauth/OAuth2AccessTokenJsonExtractor.java
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright (C) 2018 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.googlesource.gerrit.plugins.oauth;
-
-import static org.scribe.model.OAuthConstants.ACCESS_TOKEN;
-
-import com.google.common.annotations.VisibleForTesting;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import org.scribe.exceptions.OAuthException;
-import org.scribe.extractors.AccessTokenExtractor;
-import org.scribe.model.Token;
-import org.scribe.utils.Preconditions;
-
-class OAuth2AccessTokenJsonExtractor implements AccessTokenExtractor {
-  private static final Pattern ACCESS_TOKEN_REGEX_PATTERN =
-      Pattern.compile("\"" + ACCESS_TOKEN + "\"\\s*:\\s*\"(\\S*?)\"");
-
-  private OAuth2AccessTokenJsonExtractor() {}
-
-  private static final AccessTokenExtractor INSTANCE = new OAuth2AccessTokenJsonExtractor();
-
-  static AccessTokenExtractor instance() {
-    return INSTANCE;
-  }
-
-  @VisibleForTesting
-  @Override
-  public Token extract(String response) {
-    Preconditions.checkEmptyString(response, "Cannot extract a token from a null or empty String");
-    Matcher matcher = ACCESS_TOKEN_REGEX_PATTERN.matcher(response);
-    if (matcher.find()) {
-      return new Token(matcher.group(1), "", response);
-    }
-    throw new OAuthException("Cannot extract an access token. Response was: " + response);
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/oauth/Office365Api.java b/src/main/java/com/googlesource/gerrit/plugins/oauth/Office365Api.java
index 8a28520..7e9bef8 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/oauth/Office365Api.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/oauth/Office365Api.java
@@ -14,49 +14,23 @@
 
 package com.googlesource.gerrit.plugins.oauth;
 
-import static org.scribe.utils.OAuthEncoder.encode;
-
-import org.scribe.builder.api.DefaultApi20;
-import org.scribe.extractors.AccessTokenExtractor;
-import org.scribe.model.OAuthConfig;
-import org.scribe.model.Verb;
-import org.scribe.oauth.OAuthService;
-import org.scribe.utils.Preconditions;
+import com.github.scribejava.core.builder.api.DefaultApi20;
+import com.github.scribejava.core.oauth2.clientauthentication.ClientAuthentication;
+import com.github.scribejava.core.oauth2.clientauthentication.RequestBodyAuthenticationScheme;
 
 public class Office365Api extends DefaultApi20 {
-  private static final String AUTHORIZE_URL =
-      "https://login.microsoftonline.com/organizations/oauth2/v2.0/authorize?client_id=%s&response_type=code&redirect_uri=%s&scope=%s";
-
   @Override
   public String getAccessTokenEndpoint() {
     return "https://login.microsoftonline.com/organizations/oauth2/v2.0/token";
   }
 
   @Override
-  public String getAuthorizationUrl(OAuthConfig config) {
-    Preconditions.checkValidUrl(
-        config.getCallback(),
-        "Must provide a valid url as callback. Office365 does not support OOB");
-    Preconditions.checkEmptyString(
-        config.getScope(),
-        "Must provide a valid value as scope. Office365 does not support no scope");
-
-    return String.format(
-        AUTHORIZE_URL, config.getApiKey(), encode(config.getCallback()), encode(config.getScope()));
+  public String getAuthorizationBaseUrl() {
+    return "https://login.microsoftonline.com/organizations/oauth2/v2.0/authorize";
   }
 
   @Override
-  public Verb getAccessTokenVerb() {
-    return Verb.POST;
-  }
-
-  @Override
-  public OAuthService createService(OAuthConfig config) {
-    return new OAuth20ServiceImpl(this, config);
-  }
-
-  @Override
-  public AccessTokenExtractor getAccessTokenExtractor() {
-    return OAuth2AccessTokenJsonExtractor.instance();
+  public ClientAuthentication getClientAuthentication() {
+    return RequestBodyAuthenticationScheme.instance();
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/oauth/Office365OAuthService.java b/src/main/java/com/googlesource/gerrit/plugins/oauth/Office365OAuthService.java
index e1ccd29..0b22776 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/oauth/Office365OAuthService.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/oauth/Office365OAuthService.java
@@ -14,13 +14,20 @@
 
 package com.googlesource.gerrit.plugins.oauth;
 
+import static com.google.gerrit.server.OutputFormat.JSON;
+
+import com.github.scribejava.core.builder.ServiceBuilder;
+import com.github.scribejava.core.model.OAuth2AccessToken;
+import com.github.scribejava.core.model.OAuthRequest;
+import com.github.scribejava.core.model.Response;
+import com.github.scribejava.core.model.Verb;
+import com.github.scribejava.core.oauth.OAuth20Service;
 import com.google.common.base.CharMatcher;
 import com.google.gerrit.extensions.annotations.PluginName;
 import com.google.gerrit.extensions.auth.oauth.OAuthServiceProvider;
 import com.google.gerrit.extensions.auth.oauth.OAuthToken;
 import com.google.gerrit.extensions.auth.oauth.OAuthUserInfo;
 import com.google.gerrit.extensions.auth.oauth.OAuthVerifier;
-import com.google.gerrit.server.OutputFormat;
 import com.google.gerrit.server.config.CanonicalWebUrl;
 import com.google.gerrit.server.config.PluginConfig;
 import com.google.gerrit.server.config.PluginConfigFactory;
@@ -30,14 +37,8 @@
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
 import java.io.IOException;
+import java.util.concurrent.ExecutionException;
 import javax.servlet.http.HttpServletResponse;
-import org.scribe.builder.ServiceBuilder;
-import org.scribe.model.OAuthRequest;
-import org.scribe.model.Response;
-import org.scribe.model.Token;
-import org.scribe.model.Verb;
-import org.scribe.model.Verifier;
-import org.scribe.oauth.OAuthService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -49,7 +50,7 @@
   private static final String PROTECTED_RESOURCE_URL = "https://graph.microsoft.com/v1.0/me";
   private static final String SCOPE =
       "openid offline_access https://graph.microsoft.com/user.readbasic.all";
-  private final OAuthService service;
+  private final OAuth20Service service;
   private final String canonicalWebUrl;
   private final boolean useEmailAsUsername;
 
@@ -62,13 +63,11 @@
     this.canonicalWebUrl = CharMatcher.is('/').trimTrailingFrom(urlProvider.get()) + "/";
     this.useEmailAsUsername = cfg.getBoolean(InitOAuth.USE_EMAIL_AS_USERNAME, false);
     this.service =
-        new ServiceBuilder()
-            .provider(Office365Api.class)
-            .apiKey(cfg.getString(InitOAuth.CLIENT_ID))
+        new ServiceBuilder(cfg.getString(InitOAuth.CLIENT_ID))
             .apiSecret(cfg.getString(InitOAuth.CLIENT_SECRET))
             .callback(canonicalWebUrl + "oauth")
-            .scope(SCOPE)
-            .build();
+            .defaultScope(SCOPE)
+            .build(new Office365Api());
     if (log.isDebugEnabled()) {
       log.debug("OAuth2: canonicalWebUrl={}", canonicalWebUrl);
       log.debug("OAuth2: scope={}", SCOPE);
@@ -79,39 +78,43 @@
   @Override
   public OAuthUserInfo getUserInfo(OAuthToken token) throws IOException {
     OAuthRequest request = new OAuthRequest(Verb.GET, PROTECTED_RESOURCE_URL);
-    request.addHeader("Accept", "*/*");
-    request.addHeader("Authorization", "Bearer " + token.getToken());
-    Response response = request.send();
-    if (response.getCode() != HttpServletResponse.SC_OK) {
-      throw new IOException(
-          String.format(
-              "Status %s (%s) for request %s",
-              response.getCode(), response.getBody(), request.getUrl()));
-    }
-    JsonElement userJson =
-        OutputFormat.JSON.newGson().fromJson(response.getBody(), JsonElement.class);
-    if (log.isDebugEnabled()) {
-      log.debug("User info response: {}", response.getBody());
-    }
-    if (userJson.isJsonObject()) {
-      JsonObject jsonObject = userJson.getAsJsonObject();
-      JsonElement id = jsonObject.get("id");
-      if (id == null || id.isJsonNull()) {
-        throw new IOException("Response doesn't contain id field");
-      }
-      JsonElement email = jsonObject.get("mail");
-      JsonElement name = jsonObject.get("displayName");
-      String login = null;
+    OAuth2AccessToken t = new OAuth2AccessToken(token.getToken(), token.getRaw());
+    service.signRequest(t, request);
 
-      if (useEmailAsUsername && !email.isJsonNull()) {
-        login = email.getAsString().split("@")[0];
+    JsonElement userJson = null;
+    try (Response response = service.execute(request)) {
+      if (response.getCode() != HttpServletResponse.SC_OK) {
+        throw new IOException(
+            String.format(
+                "Status %s (%s) for request %s",
+                response.getCode(), response.getBody(), request.getUrl()));
       }
-      return new OAuthUserInfo(
-          OFFICE365_PROVIDER_PREFIX + id.getAsString() /*externalId*/,
-          login /*username*/,
-          email == null || email.isJsonNull() ? null : email.getAsString() /*email*/,
-          name == null || name.isJsonNull() ? null : name.getAsString() /*displayName*/,
-          null);
+      userJson = JSON.newGson().fromJson(response.getBody(), JsonElement.class);
+      if (log.isDebugEnabled()) {
+        log.debug("User info response: {}", response.getBody());
+      }
+      if (userJson.isJsonObject()) {
+        JsonObject jsonObject = userJson.getAsJsonObject();
+        JsonElement id = jsonObject.get("id");
+        if (id == null || id.isJsonNull()) {
+          throw new IOException("Response doesn't contain id field");
+        }
+        JsonElement email = jsonObject.get("mail");
+        JsonElement name = jsonObject.get("displayName");
+        String login = null;
+
+        if (useEmailAsUsername && !email.isJsonNull()) {
+          login = email.getAsString().split("@")[0];
+        }
+        return new OAuthUserInfo(
+            OFFICE365_PROVIDER_PREFIX + id.getAsString() /*externalId*/,
+            login /*username*/,
+            email == null || email.isJsonNull() ? null : email.getAsString() /*email*/,
+            name == null || name.isJsonNull() ? null : name.getAsString() /*displayName*/,
+            null);
+      }
+    } catch (ExecutionException | InterruptedException e) {
+      throw new RuntimeException("Cannot retrieve user info resource", e);
     }
 
     throw new IOException(String.format("Invalid JSON '%s': not a JSON Object", userJson));
@@ -119,15 +122,20 @@
 
   @Override
   public OAuthToken getAccessToken(OAuthVerifier rv) {
-    Verifier vi = new Verifier(rv.getValue());
-    Token to = service.getAccessToken(null, vi);
-    OAuthToken result = new OAuthToken(to.getToken(), to.getSecret(), to.getRawResponse());
-    return result;
+    try {
+      OAuth2AccessToken accessToken = service.getAccessToken(rv.getValue());
+      return new OAuthToken(
+          accessToken.getAccessToken(), accessToken.getTokenType(), accessToken.getRawResponse());
+    } catch (InterruptedException | ExecutionException | IOException e) {
+      String msg = "Cannot retrieve access token";
+      log.error(msg, e);
+      throw new RuntimeException(msg, e);
+    }
   }
 
   @Override
   public String getAuthorizationUrl() {
-    String url = service.getAuthorizationUrl(null);
+    String url = service.getAuthorizationUrl();
     return url;
   }
 
diff --git a/src/test/java/com/googlesource/gerrit/plugins/oauth/GithubApiUrlTest.java b/src/test/java/com/googlesource/gerrit/plugins/oauth/GithubApiUrlTest.java
index 64e4916..ba3f2f7 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/oauth/GithubApiUrlTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/oauth/GithubApiUrlTest.java
@@ -62,11 +62,12 @@
     }
     rootUrl = CharMatcher.is('/').trimTrailingFrom(rootUrl) + "/";
     return String.format(
-        "%slogin/oauth/authorize?client_id=%s&redirect_uri=%s%s",
+        "%slogin/oauth/authorize?response_type=code&client_id=%s&redirect_uri=%s%s&scope=%s",
         rootUrl,
         TEST_CLIENT_ID,
         URLEncoder.encode(CANONICAL_URL, StandardCharsets.UTF_8.name()),
-        URLEncoder.encode("/oauth", StandardCharsets.UTF_8.name()));
+        URLEncoder.encode("/oauth", StandardCharsets.UTF_8.name()),
+        URLEncoder.encode(GitHubOAuthService.SCOPE, StandardCharsets.UTF_8.name()));
   }
 
   @Test
diff --git a/src/test/java/com/googlesource/gerrit/plugins/oauth/OAuth2AccessTokenJsonExtractorTest.java b/src/test/java/com/googlesource/gerrit/plugins/oauth/OAuth2AccessTokenJsonExtractorTest.java
deleted file mode 100644
index ccc1db3..0000000
--- a/src/test/java/com/googlesource/gerrit/plugins/oauth/OAuth2AccessTokenJsonExtractorTest.java
+++ /dev/null
@@ -1,70 +0,0 @@
-// Copyright (C) 2018 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.googlesource.gerrit.plugins.oauth;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.gerrit.testing.GerritJUnit.assertThrows;
-import static org.junit.Assert.assertEquals;
-import static org.scribe.model.OAuthConstants.ACCESS_TOKEN;
-
-import org.junit.Test;
-import org.scribe.exceptions.OAuthException;
-import org.scribe.extractors.AccessTokenExtractor;
-import org.scribe.model.Token;
-
-public class OAuth2AccessTokenJsonExtractorTest {
-  private static final AccessTokenExtractor extractor = OAuth2AccessTokenJsonExtractor.instance();
-  private static final String TOKEN = "I0122HHJKLEM21F3WLPYHDKGKZULAUO4SGMV3ABKFTDT3T3X";
-  private static final String RESPONSE = "{\"" + ACCESS_TOKEN + "\":\"" + TOKEN + "\"}'";
-  private static final String RESPONSE_NON_JSON = ACCESS_TOKEN + "=" + TOKEN;
-  private static final String RESPONSE_WITH_BLANKS =
-      "{ \"" + ACCESS_TOKEN + "\" : \"" + TOKEN + "\"}'";
-  private static final String MESSAGE = "Cannot extract a token from a null or empty String";
-
-  @Test
-  public void parseResponse() throws Exception {
-    Token token = extractor.extract(RESPONSE);
-    assertEquals(token.getToken(), TOKEN);
-  }
-
-  @Test
-  public void parseResponseWithBlanks() throws Exception {
-    Token token = extractor.extract(RESPONSE_WITH_BLANKS);
-    assertEquals(token.getToken(), TOKEN);
-  }
-
-  @Test
-  public void failParseNonJsonResponse() throws Exception {
-    OAuthException thrown =
-        assertThrows(OAuthException.class, () -> extractor.extract(RESPONSE_NON_JSON));
-    assertThat(thrown)
-        .hasMessageThat()
-        .contains("Cannot extract an access token. Response was: " + RESPONSE_NON_JSON);
-  }
-
-  @Test
-  public void shouldThrowExceptionIfForNullParameter() throws Exception {
-    IllegalArgumentException thrown =
-        assertThrows(IllegalArgumentException.class, () -> extractor.extract(null));
-    assertThat(thrown).hasMessageThat().contains(MESSAGE);
-  }
-
-  @Test
-  public void shouldThrowExceptionIfForEmptyString() throws Exception {
-    IllegalArgumentException thrown =
-        assertThrows(IllegalArgumentException.class, () -> extractor.extract(""));
-    assertThat(thrown).hasMessageThat().contains(MESSAGE);
-  }
-}