Xamarin Android CookieManager unable to remove cookies - android

Xamarin Android CookieManager.hasCookies() returns false, indicating there are no cookies.
public static async Task<string> SignOut(){
IEnumerable<IAccount> accounts = await App.IdentityClientApp.GetAccountsAsync();
foreach (IAccount account in accounts)
{
await App.IdentityClientApp.RemoveAsync(account);
}
DependencyService.Get<ILogoutHelper>().LogoutSSO();
graphClient = null;
TokenForUser = null;
return "";
}
public class LogoutHelper : ILogoutHelper
{
public void LogoutSSO()
{
CookieManager.Instance.RemoveAllCookies(null);
CookieManager.Instance.RemoveAllCookie();
CookieManager.Instance.RemoveSessionCookie();
CookieManager.Instance.Flush();
Device.BeginInvokeOnMainThread(() => new Android.Webkit.WebView(null).ClearCache(true));
}
}
I want to clear cookies on the browser of the OS so that MSAL login credentials are not cached.

Fixed.
Switching from Chrome Custom Tabs to Embedded (WebView?) allows you to manage cookies through CookieManager.
App.UiParent = new UIParent(Xamarin.Forms.Forms.Context as Activity, true);
https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/MSAL.NET-uses-web-browser#choosing-between-embedded-web-browser-or-system-browser-on-xamarinandroid

Related

Accessing Web API from Hybrid Mobile App, Blazor Mobile

I'm working on a Blazor Hybrid App and currently trying to access a .NET Web API from my phone.
I have deployed a .NET Web Application to IIS. API returns just a WeatherForecast data (for those who're not familiar, data type is already defined in project and comes with the template) in JSON.
API Response is something like this:
[
{"date":"2020-09-18T15:55:27.4724752+03:00","temperatureC":-6,"temperatureF":22,"summary":"Hot"},
{"date":"2020-09-19T15:55:27.4725087+03:00","temperatureC":27,"temperatureF":80,"summary":"Bracing"},
{"date":"2020-09-20T15:55:27.4725155+03:00","temperatureC":54,"temperatureF":129,"summary":"Bracing"},
{"date":"2020-09-21T15:55:27.4725221+03:00","temperatureC":1,"temperatureF":33,"summary":"Scorching"},
{"date":"2020-09-22T15:55:27.4725302+03:00","temperatureC":-3,"temperatureF":27,"summary":"Chilly"}
]
I deployed it to my localhost at port 3004. So both in my PC's browser and my mobile phone's browser I can successfully reach to this address and get this response.
However in my mobile application there is a method responsible for retrieving the data, defined as:
AppState.cs:
public async Task<List<WeatherForecast>> GetForecastAsync()
{
return await _http.GetFromJsonAsync<List<WeatherForecast>>("http://192.168.1.22:3004/weatherforecast");
}
and this is called from Index.razor:
#inject AppState appState
#if(todos == null){
<p> Loading </p>
}
else {
// loop todos in foreach
}
#code {
List<Todo> todos;
protected override async Task OnInitializedAsync()
{
todos = await appState.GetForecastAsync();
appState.OnChange += UpdateState;
}
This GET Request returns null. I've tried it with JSON placeholder from https://jsonplaceholder.typicode.com/todos/ (Changed the WeatherForecast to Todo of course) there was no problem!
My Attemps
For possible solutions I've tried to
change my local IP to 10.0.2.2:3004 since I'm on the android phone but no use.
I've tried with both http:// and https:// but still no use.
Configure CORS to allow any origins in the API :
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options => options.AddDefaultPolicy(builder => builder.AllowAnyOrigin()));
services.AddControllers();
}
//...
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
//...
app.UseHttpsRedirection();
app.UseRouting();
app.UseCors();
app.UseAuthorization();
//...
}
How can I reach the API from the mobile app?
After few hours of researching and trial and error I found it.
So I'll explain the flow step by step:
First when the page we want opens, we want to fetch the data from a remote database. In my case I call it from Index.razor, my home page:
Index.razor
#page "/"
#inject AppState appState
#implements IDisposable
#if (forecasts== null)
{
<p>Null</p>
}
else
{
#foreach (var forecast in forecasts)
{
<p>#forecast .Date</p>
<p>#forecast .TemperatureC</p>
<p>#forecast .TemperatureF</p>
<p>#forecast .Summary</p>
}
}
#code {
List<WeatherForecast> forecasts;
protected override async Task OnInitializedAsync()
{
forecasts = await appState.GetForecastAsync(); // We call the Get Forecast Async via appState
appState.OnChange += UpdateState;
}
public void Dispose() => appState.OnChange -= UpdateState;
private async Task UpdateState()
{
StateHasChanged();
}
public class WeatherForecast
{
public DateTime Date { get; set; }
public int TemperatureC { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
public string Summary { get; set; }
}
}
We need the AppState because we need to keep track of state, so it's like a state manager.
After that we jump to AppState.cs:
class AppState
{
HttpClient _http;
public AppState()
{
_http = new HttpClient() { BaseAddress = new Uri("http://192.168.1.22:3000/") };
public async Task<List<WeatherForecast>> GetForecastAsync()
{
try
{
return await _http.GetFromJsonAsync<List<WeatherForecast>>("weatherforecast");
}
catch (Exception ex)
{
Debug.WriteLine(#"\tERROR {0}", ex.Message);
}
return new List<WeatherForecast>();
}
//There is also OnChange method here but it's irrelevant.
}
I've downloaded ISSexpress-proxy and proxied the api to another port because I was having "Invalid hostname" error.
When initializing HttpClient I gave the baseAddress as my local IP, not 10.0.2.2 where you use to access to localhost on Android.
I was still getting errors on HTTP communication. In AndroidManifest.xml file I added android:targetSandboxVersion="1" to <manifest> and android:usesCleartextTraffic="true" to <application>. Thanks to this answer I found how to solve it.
Final Result

How to validate mobile app facebook/google access token on spring boot 2.0 OAuth2 Authorization server?

I'm trying to validate Android/iOS client phone number or email address using Facebook Account-Kit service. I'm not sure how to validate the authorization code or access token with spring boot based back-end server and return the my own access token.
Between, I have gone thorough this blog https://www.baeldung.com/spring-security-5-oauth2-login, but it session based. I'm not clear how to change this to stateless (e.g /oauth/token).
Could anyone please let me know how to solve the issue ?
Reference : [https://developers.facebook.com/docs/accountkit/graphapi][1]
Here is my code :
#Configuration
#EnableOAuth2Client
public class SocialConfig extends WebSecurityConfigurerAdapter {
#Autowired
OAuth2ClientContext oauth2ClientContext;
private String[] PUBLIC_URL = { "/*", "/api/v1/account/validate", "login/accountkit", "/api/v1/account" };
#Override
protected void configure(HttpSecurity http) throws Exception {
// super.configure(http);
http.authorizeRequests()
.antMatchers(PUBLIC_URL).permitAll()
.anyRequest().authenticated()
.and().csrf()
.disable()
.addFilterBefore(ssoFilter(), BasicAuthenticationFilter.class);
}
private Filter ssoFilter() {
OAuth2ClientAuthenticationProcessingFilter filter = new OAuth2ClientAuthenticationProcessingFilter(
"/login/accountkit");
OAuth2ProtectedResourceDetails accountkit = accountKit();
OAuth2RestTemplate template = new OAuth2RestTemplate(accountkit, oauth2ClientContext);
filter.setRestTemplate(template);
UserInfoTokenServices userInfo = new UserInfoTokenServices(accountKitResource().getUserInfoUri(),
accountkit.getClientId());
userInfo.setRestTemplate(template);
filter.setTokenServices(userInfo);
return filter;
}
#Bean
#ConfigurationProperties("accountkit.client")
protected OAuth2ProtectedResourceDetails accountKit() {
AuthorizationCodeResourceDetails resource = new AuthorizationCodeResourceDetails();
resource.setAccessTokenUri("https://graph.accountkit.com/v1.2/me");
resource.setUserAuthorizationUri("https://graph.accountkit.com/v1.2/access_token");
resource.setClientId("AA|xxxx|xxx");
resource.setGrantType("authorization_code");
resource.setTokenName("access_token");
resource.setAuthenticationScheme(AuthenticationScheme.form);
resource.setPreEstablishedRedirectUri("http://localhost:8080/login/accountkit");
return resource;
}
#Bean
#ConfigurationProperties("accountkit.resource")
protected ResourceServerProperties accountKitResource() {
return new ResourceServerProperties();
}
}

Using cached Cognito identity from Xamarin

When I first log into my app, I go through the following code:
auth = new Xamarin.Auth.OAuth2Authenticator(
"my-google-client-id.apps.googleusercontent.com",
string.Empty,
"openid",
new System.Uri("https://accounts.google.com/o/oauth2/v2/auth"),
new System.Uri("com.enigmadream.storyvoque:/oauth2redirect"),
new System.Uri("https://www.googleapis.com/oauth2/v4/token"),
isUsingNativeUI: true);
auth.Completed += Auth_Completed;
StartActivity(auth.GetUI(this));
Which triggers this activity:
[Activity(Label = "GoodleAuthInterceptor")]
[IntentFilter(actions: new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault, Intent.CategoryBrowsable },
DataSchemes = new[] { "com.enigmadream.storyvoque" }, DataPaths = new[] { "/oauth2redirect" })]
public class GoodleAuthInterceptor : Activity
{
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
Android.Net.Uri uri_android = Intent.Data;
Uri uri_netfx = new Uri(uri_android.ToString());
MainActivity.auth?.OnPageLoading(uri_netfx);
Finish();
}
}
And finally this code to link the account to Cognito:
private void Auth_Completed(object sender, Xamarin.Auth.AuthenticatorCompletedEventArgs e)
{
if (e.IsAuthenticated)
{
var idToken = e.Account.Properties["id_token"];
credentials.AddLogin("accounts.google.com", idToken);
AmazonCognitoIdentityClient cli = new AmazonCognitoIdentityClient(credentials, RegionEndpoint.USEast2);
var req = new Amazon.CognitoIdentity.Model.GetIdRequest();
req.Logins.Add("accounts.google.com", idToken);
req.IdentityPoolId = "us-east-2:79ebf8e1-97de-4d1c-959a-xxxxxxxxxxxx";
cli.GetIdAsync(req).ContinueWith((task) =>
{
if ((task.Status == TaskStatus.RanToCompletion) && (task.Result != null))
{
ShowMessage(string.Format("Identity {0} retrieved", task.Result.IdentityId));
}
else
ShowMessage(task.Exception.InnerException != null ? task.Exception.InnerException.Message : task.Exception.Message);
});
}
else
ShowMessage("Login cancelled");
}
This all works great, and after the login, I am able to use my identity/credentials to retrieve data from DynamoDB. With this object:
Amazon.DynamoDBv2.AmazonDynamoDBClient ddbc = new Amazon.DynamoDBv2.AmazonDynamoDBClient(credentials, RegionEndpoint.USEast2);
The second time I run my app, this code runs:
if (!string.IsNullOrEmpty(credentials.GetCachedIdentityId()) || credentials.CurrentLoginProviders.Length > 0)
{
if (!bDidLogin)
{
var idToken = credentials.GetIdentityId();
ShowMessage(string.Format("I still remember you're {0} ", idToken));
And if I try to use the credentials with DynamoDB (or anything, I assume) at this point, I get errors that I don't have access to the identity. I have to logout (credentials.Clear()) and login again to obtain proper credentials.
I could require that a user go through the whole login process every time my app runs, but that's a real pain because the Google login process requires the user to know how to manually close the web browser to get back to the application after authenticating. Is there something I'm missing about the purpose and usage of cached credentials? When I use most apps, they aren't requiring me to log into my Google account every time and close a web browser just to access their server resources.
It looks like the refresh token needs to be submitted back to the OAuth2 provider to get an updated id token to add to the credentials object. First I added some code to save and load the refresh_token in a config.json file:
private Dictionary<string, string> config;
const string CONFIG_FILE = "config.json";
private void Auth_Completed(object sender, Xamarin.Auth.AuthenticatorCompletedEventArgs e)
{
if (e.IsAuthenticated)
{
var idToken = e.Account.Properties["id_token"];
if (e.Account.Properties.ContainsKey("refresh_token"))
{
if (config == null)
config = new Dictionary<string, string>();
config["refresh_token"] = e.Account.Properties["refresh_token"];
WriteConfig();
}
credentials.AddLogin("accounts.google.com", idToken);
CognitoLogin(idToken).ContinueWith((t) =>
{
try
{
t.Wait();
}
catch (Exception ex)
{
ShowMessage(ex.Message);
}
});
}
else
ShowMessage("Login cancelled");
}
void WriteConfig()
{
using (var configWriter = new System.IO.StreamWriter(
Application.OpenFileOutput(CONFIG_FILE, Android.Content.FileCreationMode.Private)))
{
configWriter.Write(ThirdParty.Json.LitJson.JsonMapper.ToJson(config));
configWriter.Close();
}
}
public void Login()
{
try
{
if (!string.IsNullOrEmpty(credentials.GetCachedIdentityId()) || credentials.CurrentLoginProviders.Length > 0)
{
if (!bDidLogin)
{
var idToken = credentials.GetIdentityId();
if (ReadConfig())
{
LoginRefreshAsync().ContinueWith((t) =>
{
try
{
t.Wait();
if (!t.Result)
FullLogin();
}
catch (Exception ex)
{
ShowMessage(ex.Message);
}
});
}
else
{
credentials.Clear();
FullLogin();
}
}
}
else
FullLogin();
bDidLogin = true;
}
catch(Exception ex)
{
ShowMessage(string.Format("Error logging in: {0}", ex.Message));
}
}
private bool ReadConfig()
{
bool bFound = false;
foreach (string filename in Application.FileList())
if (string.Compare(filename, CONFIG_FILE, true) == 0)
{
bFound = true;
break;
}
if (!bFound)
return false;
using (var configReader = new System.IO.StreamReader(Application.OpenFileInput(CONFIG_FILE)))
{
config = ThirdParty.Json.LitJson.JsonMapper.ToObject<Dictionary<string, string>>(configReader.ReadToEnd());
return true;
}
}
Then refactored the code that initiates the interactive login into a separate function:
public void FullLogin()
{
auth = new Xamarin.Auth.OAuth2Authenticator(CLIENTID_GOOGLE, string.Empty, "openid",
new Uri("https://accounts.google.com/o/oauth2/v2/auth"),
new Uri("com.enigmadream.storyvoque:/oauth2redirect"),
new Uri("https://accounts.google.com/o/oauth2/token"),
isUsingNativeUI: true);
auth.Completed += Auth_Completed;
StartActivity(auth.GetUI(this));
}
Refactored the code that retrieves a Cognito identity into its own function:
private async Task CognitoLogin(string idToken)
{
AmazonCognitoIdentityClient cli = new AmazonCognitoIdentityClient(credentials, RegionEndpoint.USEast2);
var req = new Amazon.CognitoIdentity.Model.GetIdRequest();
req.Logins.Add("accounts.google.com", idToken);
req.IdentityPoolId = ID_POOL;
try
{
var result = await cli.GetIdAsync(req);
ShowMessage(string.Format("Identity {0} retrieved", result.IdentityId));
}
catch (Exception ex)
{
ShowMessage(ex.Message);
}
}
And finally implemented a function that can retrieve a new token based on the refresh token, insert it into the current Cognito credentials, and get an updated Cognito identity.
private async Task<bool> LoginRefreshAsync()
{
string tokenUrl = "https://accounts.google.com/o/oauth2/token";
try
{
using (System.Net.Http.HttpClient client = new System.Net.Http.HttpClient())
{
string contentString = string.Format(
"client_id={0}&grant_type=refresh_token&refresh_token={1}&",
Uri.EscapeDataString(CLIENTID_GOOGLE),
Uri.EscapeDataString(config["refresh_token"]));
System.Net.Http.HttpContent content = new System.Net.Http.ByteArrayContent(
System.Text.Encoding.UTF8.GetBytes(contentString));
content.Headers.Add("content-type", "application/x-www-form-urlencoded");
System.Net.Http.HttpResponseMessage msg = await client.PostAsync(tokenUrl, content);
string result = await msg.Content.ReadAsStringAsync();
string idToken = System.Json.JsonValue.Parse(result)["id_token"];
credentials.AddLogin("accounts.google.com", idToken);
/* EDIT -- discovered this is not necessary! */
// await CognitoLogin(idToken);
return true;
}
}
catch (Exception ex)
{
ShowMessage(ex.Message);
return false;
}
}
I'm not sure if this is optimal or even correct, but it seems to work. I can use the resulting credentials to access DynamoDB without having to prompt the user for permission/credentials again.
There's a very different solution I'm trying to fit with the other answer. But it's so different, I'm adding it as a separate answer.
It appears the problem was not so much related to needing to explicitly use a refresh token to get an updated access token (I think this is done implicitly), but rather needing to remember the identity token. So rather than include all the complexity of manually applying a refresh token, all that's needed is to store the identity token (which can be done in a way similar to how the refresh token was being stored). Then we just need to add that same identity token back to the credentials object when it's missing.
if (!string.IsNullOrEmpty(credentials.GetCachedIdentityId()) || credentials.CurrentLoginProviders.Length > 0)
{
if (config.Read())
{
if (config["id_token"] != null)
credentials.AddLogin(currentProvider.Name, config["id_token"]);
Edit: The problem of needing to use a refresh token does still exist. This code works if the token hasn't expired, but attempting to use these credentials after the token has expired will fail, so there is still some need to use a refresh token somehow in some cases.

AppAuth oidc invalid State error when authenticating against identity server

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.
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
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.

Couchbase facebook pull authenticator

I am using couchbase mobile for an application and I want to use facebook for authentication. As per documentation, couchbase offers it's own implementation for authentication, the only required thing would be the token which I retrieve from the android facebook login flow.
The code for Synchronize class looks something like this:
public class Synchronize {
public Replication pullReplication;
public Replication pushReplication;
public static class Builder {
public Replication pullReplication;
public Replication pushReplication;
public Builder(Database database, String url, Boolean continuousPull) {
if (pullReplication == null && pushReplication == null) {
URL syncUrl;
try {
syncUrl = new URL(url);
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
pullReplication = database.createPullReplication(syncUrl);
pullReplication.setContinuous(true);
pushReplication = database.createPushReplication(syncUrl);
pushReplication.setContinuous(true);
}
}
public Builder facebookAuth(String token) {
if (!TextUtils.isEmpty(token)) {
Authenticator facebookAuthenticator = AuthenticatorFactory.createFacebookAuthenticator(token);
pullReplication.setAuthenticator(facebookAuthenticator);
pushReplication.setAuthenticator(facebookAuthenticator);
}
return this;
}
public Builder basicAuth(String username, String password) {
Authenticator basicAuthenticator = AuthenticatorFactory.createBasicAuthenticator(username, password);
pullReplication.setAuthenticator(basicAuthenticator);
pushReplication.setAuthenticator(basicAuthenticator);
return this;
}
public Builder addChangeListener(Replication.ChangeListener changeListener) {
pullReplication.addChangeListener(changeListener);
pushReplication.addChangeListener(changeListener);
return this;
}
public Synchronize build() {
return new Synchronize(this);
}
}
private Synchronize(Builder builder) {
pullReplication = builder.pullReplication;
pushReplication = builder.pushReplication;
}
public void start() {
pullReplication.start();
pushReplication.start();
}
public void destroyReplications() {
if (pullReplication != null && pushReplication != null) {
pullReplication.stop();
pushReplication.stop();
pullReplication.deleteCookie("SyncGatewaySession");
pushReplication.deleteCookie("SyncGatewaySession");
pullReplication = null;
pushReplication = null;
}
}
}
And I use it like this:
...
public void startReplicationSync(String facebookAccessToken) {
if (sync != null) {
sync.destroyReplications();
}
final String url = BuildConfig.URL_HOST + ":" + BuildConfig.URL_PORT + "/" + DATABASE_NAME;
sync = new Synchronize.Builder(databaseManager.getDatabase(), url, true)
.facebookAuth(facebookAccessToken)
.addChangeListener(getReplicationChangeListener())
.build();
sync.start();
}
...
My sync gateway json config file:
{
"interface":":4984",
"adminInterface":":4985",
"log":["REST"],
"facebook":{
"register" : true
},
"databases":{
"sync_gateway":{
"server":"http://localhost:8091",
"bucket":"sync_gateway",
"users": {
"GUEST": {"disabled": false}
},
"sync":`function(doc) {channel(doc.channels);}`
}
}
}
I also tried with "GUEST": {"disabled": true}, no luck
My problem is that if I do this
pullReplication.setAuthenticator(facebookAuthenticator);
pushReplication.setAuthenticator(facebookAuthenticator);
Nothing will ever get replicated/pulled from the server. However if I don't set an authenticator, everything is pulled. Is it something I am doing wrong? I really need to use the authenticator in order to prevent some documents to not being replicated for non-authenticated users.
Note! The token is good, as if I am looking in the users section of sync gateway admin, I can see the right profile id of the logged in user token I passed to the couchbase facebook authenticator.
In the Sync Gateway config you provided, the Sync Function is function(doc, oldDoc) {channel(doc.channels);} which means that if the document processed by Sync Gateway contains a string(s) under the channels field, the document will be mapped to this/these channel(s). Let's assume the following config file:
{
"log": ["CRUD"],
"databases": {
"db": {
"server": "walrus:",
"users": {
"GUEST": {"disabled": false, "admin_channels": ["*"]}
},
"sync": `
function sync(doc, oldDoc) {
channel(doc.channels);
}
`
}
}
}
If the channels field doesn't exist then the document will be mapped to a channel called undefined. But the GUEST account has access to the * channel (a placeholder to represent all channels). So, all unauthenticated replications will pull all documents. Let's now introduce the facebook login field in the config file. This time, replications authenticated with a facebook token represent a new user which has only access to the ! channel by default (watch this screencast to understand the ! channel, a.k.a the public channel https://www.youtube.com/watch?v=DKmb5mj9pMI). To give a user access to other channels, you must use the access API call in the Sync Function (read more about all Sync Function API calls here).
In the case of facebook authentication, the user's facebook ID is used to represent the user name. Supposing that the document has a property holding the user's facebook ID (user_id: FACEBOOK_ID), you can map the document to a channel and give the user access to it. The new Sync Function would look like this:
function(doc, oldDoc) {
channel(doc._id);
access(doc.user_id, doc._id);
}
You can retrieve the user's facebook ID with the Facebook Android SDK and save on a document field.

Categories

Resources