1

I have a Spring application that is secured using Oauth provided by Twitch. What I am trying to do is make it so that when the user clicks the logout button they have to reenter their Twitch credentials to log back into the site. From the reading I have done my understanding of the situation is that single sign off is fairly hard to achieve with Oauth. That said, Twitch's API seems to indicate that there is a way to tell them to invalidate an Oauth token: https://dev.twitch.tv/docs/authentication#revoking-access-tokens.

I have seen some information about a prompt=login parameter in OIDC but I have not been able to find any information about it or how to use it (on top of that I'm pretty sure that will require the users to reenter their credentials EVERY time rather than simply when they logout).

My initial approach to this problem was the number 1 answer in this thread but that did not actually change anything. I still was not required to enter my credentials upon trying to access one of the restricted endpoints, Spring simply quickly reauthenticated with Twitch and sent me through.

My current approach was to directly hit the endpoint in the Twitch api with a POST request (this was inspired by a comment in this thread). This approach is also not doing anything. I will include the code of that approach below.

I would much prefer to solve this problem with something built into Spring (holding out hope that there is something that I have somehow missed). An interesting thing I have been noticing happening is that when I hit /logout my browser will be redirected to Twitch's authorization endpoint which is making me think that for some reason Spring is trying to send a token revoke request to the authorization endpoint OR is logging me back into the endpoint as soon as I am logged out of it. Thought this was worth mentioning.

Any help replicating the initially mentioned behavior would be much appreciated.

SpringSecurityConfiguration:

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
@EnableWebSecurity
public class SpringSecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    public void configure(HttpSecurity httpSecurity) throws Exception {
        //This allows users to access the "/" and "/Info" endpoints without authenticating with Twitch. To go anywhere else they will have to authenticate.
        httpSecurity.antMatcher("/**").authorizeRequests().antMatchers("/", "/Info", "/token/deletion").permitAll().anyRequest().authenticated().and().oauth2Login().and()
                //This configures logout tells spring to do the logout with the method in the logoutSuccessHandler
                .logout().logoutSuccessUrl("http://localhost:8080/token/deletion").invalidateHttpSession(true).clearAuthentication(true).deleteCookies("JSESSIONID", "JWT");
}

application.properties:

spring.mvc.view.prefix=/WEB-INF/jsp/
spring.mvc.view.suffix=.jsp

spring.security.oauth2.client.registration.twitch=twitch
spring.security.oauth2.client.registration.twitch.client-id=redacted
spring.security.oauth2.client.registration.twitch.client-secret=redacted
spring.security.oauth2.client.registration.twitch.client-authentication-method=post
spring.security.oauth2.client.registration.twitch.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.twitch.redirect-uri=http://localhost:8080/login/oauth2/code/twitch
spring.security.oauth2.client.registration.twitch.scope=user:read:email
spring.security.oauth2.client.registration.twitch.client-name=Twitch

spring.security.oauth2.client.provider.twitch.authorization-uri=https://id.twitch.tv/oauth2/authorize
spring.security.oauth2.client.provider.twitch.token-uri=https://id.twitch.tv/oauth2/token
spring.security.oauth2.client.provider.twitch.jwk-set-uri=https://id.twitch.tv/oauth2/keys
spring.security.oauth2.client.provider.twitch.user-info-uri=https://id.twitch.tv/oauth2/userinfo
spring.security.oauth2.client.provider.twitch.user-info-authentication-method=post
spring.security.oauth2.client.provider.twitch.user-name-attribute=sub

TokenRemovalController:

import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.net.ssl.HttpsURLConnection;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URL;
import java.nio.charset.StandardCharsets;


@Controller
public class TokenRemovalController {

    @RequestMapping("/token/deletion")
    public void removeTokenFromTwitch(HttpServletResponse response, @RegisteredOAuth2AuthorizedClient("twitch") OAuth2AuthorizedClient authorizedClient) throws IOException {
        //Get access token of current user
        String accessToken = authorizedClient.getAccessToken().getTokenValue();

        //POST Request to Twitch endpoint
        URL url = new URL("https://id.twitch.tv/oauth2/revoke");
        HttpsURLConnection https = (HttpsURLConnection)url.openConnection();
        https.setRequestMethod("POST");
        https.setDoOutput(true);
        https.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");

        String data = "client_id=redacted&token=" + accessToken;

        byte[] out = data.getBytes(StandardCharsets.UTF_8);

        OutputStream stream = https.getOutputStream();
        stream.write(out);

        String redirectString = "/error";
        if (https.getResponseCode() == 200) {
            redirectString = "/Info";
        }

        https.disconnect();

        response.sendRedirect(redirectString);
    }
}

Project Structure:

enter image description here

Another thing I forgot to mention was that if I delete all of my browsers cookies then I do get logged out of my site. Is it potentially possible to replicate this effect when users attempt to log out (obviously without deleting all of the users other cookies)?

Epoch
  • 49
  • 4
  • Can you confirm that the access token no longer works after revoking it? What I think is happening is that the access token DOES get revoked but the cookie in the user's browser and Twitch's OAuth server remains, which means the next time they click "login" on your site, the cookie still has an active session with Twitch's server and the user is logged in automatically (this is called the silent flow). Hence, when you delete all your cookies the session between the browser and Twitch's server is broken and you'll have to login again. – Mekswoll Dec 07 '21 at 11:16
  • Sorry for the late response - I'm actually fairly convinced at this point that the endpoint at Twitch does not behave the way I initially though. I looked at other sites that utilize OAuth with Twitch as the provider (these are the sites of multimillion dollar companies) and it looks like their logout functions do not have the feature I was pursuing. I verified this by manually sending my tokens to the revoke endpoint at Twitch and my account was not logged out of their platform. My current approach is just to educate my users and emulate the best practices exuded by companies in the space. – Epoch Dec 21 '21 at 13:21

0 Answers0