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?
3 Answers 3
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.
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.
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.
AuthenticationKeyGenerator
interface. Could you create your own implementation and use that instead?