jsf-2.2twitter4jkeycloak

Accessing user Oauth tokens returned by Keycloak


I have a Keycloak (standalone) v1.9.4.Final install setup using Wildfly 10 on an AWS instance and am trying to use keycloak (via keycloak's login page) and Twitter4j to authenticate a user with Twitter and then obviously have my application authenticate and view the users timeline, etc.

I have configured the Identity Provider (Twitter), the realm and my client application.

I also have a Twitter application setup at apps.twitter.com and the keys put into my twitter4j.properties file.

So far, I am able to:

  1. Go to my application's JSF webpage and get redirected to Keycloak's /auth login page
  2. Click the Twitter logo and login with my Twitter account (separate account from the account that owns the Twitter application)
  3. Complete the user information that Keycloak asks for
  4. After completing the user information, Keycloak successfully directs the user back to the client application (in this case, a JSF page).

The problem is, I can't figure out how to get access to the users OAuth AccessToken and AccessTokenSecret to combine with the Twitter application's ConsumerKey and ConsumerKeySecret.

Below are the relevant snippets of code:

twitterLogin.xhtml

...
#{twitterBean.getUserInfo()}
...


TwitterBean.java

@Context
private final FacesContext facesContext = FacesContext.getCurrentInstance();
private final Twitter twitter = TwitterFactory.getSingleton();

public String getUserInfo() {

    if (facesContext != null) {
        HttpSession httpSession = (HttpSession) facesContext.getExternalContext().getSession(false);
        KeycloakSecurityContext keycloakContext = (RefreshableKeycloakSecurityContext) httpSession.getAttribute(KeycloakSecurityContext.class.getName());
        queryTwitter(keycloakContext.getTokenString(), keycloakContext.getIdTokenString());
    }
}

public void queryTwitter(String accessToken, String accessTokenSecret) {
    ConfigurationBuilder cb = new ConfigurationBuilder();
    cb.setDebugEnabled(true)
            .setOAuthConsumerKey("APPLICATIONS_CONSUMER_KEY")
            .setOAuthConsumerSecret("APPLICATIONS_CONSUMER_KEY_SECRET")
            .setOAuthAccessToken(accessToken)
            .setOAuthAccessTokenSecret(accessTokenSecret);
    TwitterFactory tf = new TwitterFactory(cb.build());
    twitter = tf.getInstance();
    user = twitter.verifyCredentials();
    ...
}

Depending on how we arrange the various tokens (trying different token strings in different places), we get different errors returned by Twitter when calling verifyCredentials(), but all essentially "invalid authentication".

It's obvious to us the .getTokenString() and .getIdTokenString() methods are not the correct methods to use, but we're at a loss as to where Keycloak would provide the user's oauth tokens.

We tried to look at facesContext.getExternalContext().getRequest() and facesContext.getExternalContext().getResponse() without much luck.

My question is, when Keycloak redirects back to the client application (Service Provider) after the user authenticates with Twitter and registers the user within Keycloak, where are the oauth tokens kept so that we can gain access to them and then dually authenticate (Application/User authentication)?


Solution

  • Hopefully this code snippet helps someone...

    @PostConstruct
    public void init() {
        facesContext = FacesContext.getCurrentInstance();
    }
    
    
    public void login() {
    
        if (user == null) {
    
            if (facesContext != null) {
                HttpSession httpSession = (HttpSession) facesContext.getExternalContext().getSession(false);
                KeycloakSecurityContext keycloakContext = (RefreshableKeycloakSecurityContext) httpSession.getAttribute(KeycloakSecurityContext.class.getName());
                MyAccessToken accessToken = generateMyAccessToken(keycloakContext);
    
            }
        }
    }
    
    private MyAccessToken generateMyAccessToken(KeycloakSecurityContext keycloakContext) {
        AccessToken keycloakToken = keycloakContext.getToken();
        MyAccessToken token = new MyAccessToken();
    
        token.withEmail(keycloakToken.getEmail())
                .withExpiration(keycloakToken.getExpiration())
                .withUsername(keycloakToken.getPreferredUsername())
                .withOauthToken(getTwitterOAuthResponse(keycloakContext.getTokenString()))
                .withFirstName(keycloakToken.getGivenName())
                .withLastName(keycloakToken.getFamilyName());
    
        return token;
    }
    
    private OAuthTokenWrapper getTwitterOAuthResponse(final String tokenString) {
        ClientRequestFilter authFilter = new ClientRequestFilter() {
            @Override
            public void filter(ClientRequestContext requestContext) throws IOException {
                requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, "Bearer " + tokenString);
            }
        };
    
        Client client = ClientBuilder.newBuilder().register(authFilter).build();
        WebTarget target = client.target(getIdentityProviderTokenUrl());
    
        TwitterOAuthResponse resp = target.request().get().readEntity(TwitterOAuthResponse.class);
        return new OAuthTokenWrapper(resp.getToken(), resp.getTokenSecret());
    }
    
    private String getIdentityProviderTokenUrl() {
        return "https://login.example.com:8443/auth/realms/myapp/broker/twitter/token";
    

    }