Reputation: 21
I've configured an identity server 3 as an IdP for a project, we have 3 clients: MVC web, IOS and Android. everything is good for MVC app, using Hybrid flow.
for IOS and Android, using the native oidc client (AppAuth IOS and AppAuth android) is not working, even though I configured the flow as Hybrid with PKCE.
now when I try to make a POC on android using Xamarin, and using IdentityModel.oidcClient everything works as expected, getting access, refresh and id tokens. when using the AppAuth for IOS and android I am getting the following error:
{"type":0,"code":9,"errorDescription":"Response state param did not match request state"}
any idea what is missing ?
I am suspecting that those two native oidc clients aren't asking for shared secret of the clients, so the flow is corrupted because of that.
Upvotes: 2
Views: 3353
Reputation: 1373
The data should be the same in the mobile app and in the Identity Server,
On The Server :
new Client
{
ClientId = "myClientId",
ClientName = "myClientName",
AllowedGrantTypes = GrantTypes.CodeAndClientCredentials,
RequireConsent = false,
ClientSecrets =
{
new Secret("myClientSecret".Sha256())
},
RedirectUris = { "myRedirectUri://callback" },
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email,
IdentityServerConstants.StandardScopes.Phone,
},
AllowOfflineAccess = true
}
On click login in android :
AuthManager authManager = AuthManager.getInstance(this);
AuthorizationService authService = authManager.getAuthService();
Auth auth = authManager.getAuth();
AuthorizationRequest authRequest = new AuthorizationRequest
.Builder(
authManager.getAuthConfig(),
auth.getClientId(),
auth.getResponseType(),
Uri.parse(auth.getRedirectUri()))
.setScope(auth.getScope())
.build();
Intent authIntent = new Intent(this, LoginAuthActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, authRequest.hashCode(), authIntent, 0);
authService.performAuthorizationRequest(
authRequest,
pendingIntent);
Ask for the token :
final AuthorizationResponse resp = AuthorizationResponse.fromIntent(getIntent());
AuthorizationException ex = AuthorizationException.fromIntent(getIntent());
final AuthManager authManager = AuthManager.getInstance(this);
authManager.setAuthState(resp,ex);
if (resp != null) {
ClientSecretPost clientSecretPost = new ClientSecretPost(authManager.getAuth().getClientSecret());
TokenRequest tokenRequest = new TokenRequest
.Builder(authManager.getAuthConfig(), authManager.getAuth().getClientId())
.setAuthorizationCode(resp.authorizationCode)
.setRedirectUri(Uri.parse(authManager.getAuth().getRedirectUri()))
.build();
mAuthService = authManager.getAuthService();
mAuthService.performTokenRequest(tokenRequest, clientSecretPost, new AuthorizationService.TokenResponseCallback() {
@Override public void onTokenRequestCompleted(@Nullable TokenResponse response, @Nullable AuthorizationException ex) {
if(ex == null) {
authManager.updateAuthState(response,ex);
MyApp.Token = authManager.getAuthState().getIdToken();
startService(new Intent(LoginAuthActivity.this, TokenService.class));
Intent mainIntent = new Intent(LoginAuthActivity.this, MainActivity.class);
startActivity(mainIntent);
finish();
}
else{
Intent loginIntent = new Intent(LoginAuthActivity.this, LoginActivity.class);
startActivity(loginIntent);
finish();
}
}
});
// authorization completed
} else {
// authorization failed, check ex for more details
Intent loginIntent = new Intent(LoginAuthActivity.this, LoginActivity.class);
startActivity(loginIntent);
finish();
}
The AuthManager Class :
public class AuthManager {
private static AuthManager instance;
private AuthState mAuthState;
private Auth mAuth;
private AuthorizationServiceConfiguration mAuthConfig;
private SharedPreferencesRepository mSharedPrefRep;
private AuthorizationService mAuthService;
public static AuthManager getInstance(Context context) {
if (instance == null) {
instance = new AuthManager(context);
}
return instance;
}
private AuthManager(Context context){
mSharedPrefRep = new SharedPreferencesRepository(context);
setAuthData();
mAuthConfig = new AuthorizationServiceConfiguration(
Uri.parse(mAuth.getAuthorizationEndpointUri()),
Uri.parse(mAuth.getTokenEndpointUri()),
null);
mAuthState = mSharedPrefRep.getAuthState();
mAuthService = new AuthorizationService(context);
}
public AuthorizationServiceConfiguration getAuthConfig() {
return mAuthConfig;
}
public Auth getAuth() {
if(mAuth == null){
setAuthData();
}
return mAuth;
}
public AuthState getAuthState(){
return mAuthState;
}
public void updateAuthState(TokenResponse response, AuthorizationException ex){
mAuthState.update(response,ex);
mSharedPrefRep.saveAuthState(mAuthState);
}
public void setAuthState(AuthorizationResponse response, AuthorizationException ex){
if(mAuthState == null)
mAuthState = new AuthState(response,ex);
mSharedPrefRep.saveAuthState(mAuthState);
}
public AuthorizationService getAuthService(){
return mAuthService;
}
private void setAuthData(){
mAuth = new Auth();
mAuth.setClientId(BuildConfig.CLIENT_ID);
mAuth.setAuthorizationEndpointUri(BuildConfig.AUTHORIZSTION_END_POINT_URI);
mAuth.setClientSecret(BuildConfig.CLIENT_SECRET);
mAuth.setRedirectUri(BuildConfig.REDIRECT_URI);
mAuth.setScope(BuildConfig.SCOPE);
mAuth.setTokenEndpointUri(BuildConfig.TOKEN_END_POINT_URI);
mAuth.setResponseType(BuildConfig.RESPONSE_TYPE);
}
}
The Service here will ask for the refresh token.
I have made a sample using Identity Server 4 with AppAuth-Android you can check it here
Upvotes: 1
Reputation: 2751
We have an open issue for hybrid flow support on AppAuth-Android here. The main issue with this is that the hybrid flow is a poor fit for mobile applications, as it would require repeatedly triggering a web flow via SafariViewController / CustomTab every time the access token expires. Acquiring a refresh token to allow background update of access tokens is better for native apps.
As IdentityServer3 is a certified OpenID Connect implementation, you should be able to use the authorization code flow to obtain a refresh token.
Upvotes: 0