13

I have an API that uses oAuth2 and my own mobile apps that use this API as their backend. Since users can be logged on via multiple devices (i.e iPhone, iPad, android tablet or android phone) at the same time, I need the API to distinguish between each connection. I would like to do this via separate access tokens: each client gets a separate access token.

The problem is that the current implementation we use (spring-security-oauth2) generates a unique key based on client_id, username, and scope. So basically, when getting an access token, all clients get the same access token for the same user. This is done using DefaultAuthenticationKeyGenerator.

Is it safe to ignore the authentication key generator and simply create a new access token on each request from a client?

  • 2
    can you use scope to differentiate each client? i.e. give ios an "ios" scope, android an "android" scope, the tablet a "tablet" scope, etc. But FWIW I ended up writing my own TokenServices implementation (actually I think I made it a wrapper around the default) that generated a brand new token every time. – sea-rob Feb 9 '16 at 19:28
  • In general, though, the Spring Security OAuth2 implementation worked well for me (once I got through the XML configuration), but managing the token and authentication objects was an ongoing pain point. – sea-rob Feb 9 '16 at 19:29
  • 2
    Searching Google for "DefaultAuthenticationKeyGenerator" led me to a .java file in the spring-security-oauth library on GitHub. That class implements the AuthenticationKeyGenerator interface. Could you create your own implementation and use that instead? – Greg Burghardt Jan 12 '17 at 18:05
  • The URL to the .java file I found: github.com/spring-projects/spring-security-oauth/blob/master/… – Greg Burghardt Jan 12 '17 at 18:06
  • 2
    I am agree with @Rob you can go with devicetype in request like "android", "ios", "web" etc – Vikash Rajpurohit Jan 17 '17 at 14:18
1

Spring cloud provides already this behavior. Just add different Clients. Like iosAppClient, androidAppClient in your AuthorizationServerConfiguration class.

@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
                clients.inMemory().withClient("androidAppclient")
                    .secret("clientsecret")
                    .autoApprove(true)
                    .accessTokenValiditySeconds(120)
                    .authorizedGrantTypes("password")
                    .resourceIds("accountservice")
                    .scopes("read", "write")
                    .and()
                    .withClient("iosappclient")
                    ........

        }

In the backend you can get the clientID like the following

clientId = ((OAuth2Authentication) authentication).getOAuth2Request().getClientId();

and implement different behavior based on the clientId.

0

One answer is that each app platform is a different client, so should have a different client id. One for the iOS app, one for the web site, etc.

As to differentiating between say an iPad vs an iPhone, I would suggest not relying on the OAuth system for this.

0

I stumbled upon the same problem while developing my backend with Spring Boot and OAuth2. The problem I encountered was that, if multiple devices shared the same tokens, once one device refreshed the token, the other device would be clueless and, long story short, both devices entered in a token refresh frenzy. My solution was to replace the default AuthenticationKeyGenerator with a custom implementation which overrides DefaultAuthenticationKeyGenerator and adds a new parameter client_instance_id in the key generator mixture. My mobile clients would then send this parameter which has to be unique across app installs (iOS or Android). This is not a special requirement, since most mobile apps already track the application instance in some form.

public class EnhancedAuthenticationKeyGenerator extends DefaultAuthenticationKeyGenerator {

    public static final String PARAM_CLIENT_INSTANCE_ID = "client_instance_id";

    private static final String KEY_SUPER_KEY = "super_key";
    private static final String KEY_CLIENT_INSTANCE_ID = PARAM_CLIENT_INSTANCE_ID;

    @Override
    public String extractKey(final OAuth2Authentication authentication) {
        final String superKey = super.extractKey(authentication);

        final OAuth2Request authorizationRequest = authentication.getOAuth2Request();
        final Map<String, String> requestParameters = authorizationRequest.getRequestParameters();

        final String clientInstanceId = requestParameters != null ? requestParameters.get(PARAM_CLIENT_INSTANCE_ID) : null;
        if (clientInstanceId == null || clientInstanceId.length() == 0) {
            return superKey;
        }

        final Map<String, String> values = new LinkedHashMap<>(2);
        values.put(KEY_SUPER_KEY, superKey);
        values.put(KEY_CLIENT_INSTANCE_ID, clientInstanceId);

        return generateKey(values);
    }

}

which you would then inject in a similar manner:

final JdbcTokenStore tokenStore = new JdbcTokenStore(mDataSource);
tokenStore.setAuthenticationKeyGenerator(new EnhancedAuthenticationKeyGenerator());

The HTTP request would then look something like this

POST /oauth/token HTTP/1.1
Host: {{host}}
Authorization: Basic {{auth_client_basic}}
Content-Type: application/x-www-form-urlencoded

grant_type=password&username={{username}}&password={{password}}&client_instance_id={{instance_id}}

The benefit of using this approach is that, if the client doesn't send a client_instance_id, the default key would be generated, and if an instance is provided, the same key is returned every time for the same instance. Also, the key is platform independent. The downside would be that the MD5 digest (used internally) is called two times.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.