Parameters are empty when submitting request for adapter authentication on Android - xamarin

I use adapter authentication in my Xamarin.Forms app with the IBM MFP SDK. The adapter requires a username and a password.
In my iOS app (with the exact same shared code) everything works as it should.
In my Android app the parameters are empty (found that out using Charles / Fiddler).
I debugged the process and my Identity variable with username and password is not null and correctly filled in.
public override AdapterAuthenticationInfo GetAdapterAuthenticationParameters()
{
var parameters = new string[] { Identity.Email, Identity.Password };
var invocationData = new WorklightProcedureInvocationData("AuthAdapter", "submitAuthentication", parameters);
var authInfo = new AdapterAuthenticationInfo();
authInfo.InvocationData = invocationData;
return authInfo;
}

Can you try running your app using object array instead of string array and see if that works?
var parameters = new object[] { Identity.Email, Identity.Password };

Related

Getting an error while retrieving a blob using user assigned managed identity

We have a C# code which used to retrieve a blob from storage account. The authentication is done using user assigned service principal. These things works till December. But now we are getting some weird error as follows.
ManagedIdentityCredential authentication unavailable. The requested identity has not been assigned to this resource.
Status: 400 (Bad Request)
Content:
{"error":"invalid_request","error_description":"Identity not found"}
The managed identity has storage data blob contributor access in the storage account.
Attaching the code for reference:
public static async Task<string> GetBlobAsync()
{
string storageName = "storage account name";
Uri blobUri = new Uri("blob uri");
TokenCredential cred = new ManagedIdentityCredential("client id");
var blobClient = new BlobClient(blobUri, cred, null);
try
{
var downloadInfo = await blobClient.DownloadAsync();
using (TextReader reader = new StreamReader(downloadInfo.Value.Content))
{
string metadataBlob = await reader.ReadToEndAsync();
return metadataBlob;
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
Console.WriteLine("");
return null;
}
P:S: the three environmental variables such as app id, app secret and tenant id are correct.
I have been stuck here for almost a month. Nothing works.
This document demonstrates how to use managed identity to access App Configuration from App Service, but you can replace the App Service with any other Azure services that support managed identity. https://learn.microsoft.com/en-us/azure/azure-app-configuration/howto-integrate-azure-managed-service-identity
Here are a few things I'd like to call out
Make sure the managed identity is enabled in the Azure service where your application runs.
When you are using system assigned managed identity, you don't need to provide the client Id. You only need to provide the client Id when you use user assigned managed identity.
Make sure the managed identity is granted either App Configuration Data Reader or App Configuration Data Owner role in the access control of your App Configuration instance.
Wait for at least 15 minutes after the role assignment for the permission to propagate.
Managed identity can ONLY work when your code is running in the Azure service. It will NOT work when running locally
Try this
Uri blobUri = new Uri("blob uri");
var cred = new DefaultAzureCredential(
new DefaultAzureCredentialOptions {
ManagedIdentityClientId = "your client id" });
var blobClient = new BlobClient(blobUri, cred, null);
ref: https://learn.microsoft.com/pt-br/dotnet/api/azure.identity.defaultazurecredential?view=azure-dotnet
Option 2 (work for me)
Create Identity and add in app service
Assign RBAC "Storage Blob Data Contributor" to your storage resource.
Add Key AZURE_CLIENT_ID (clientid of the identity that was created) in Environment App Service
Code to access blob
(you don't need to specify client id in the code because it will use the AZURE_CLIENT_ID configured in the AppService)
app.MapGet("/read", async () =>
{
Uri blobUri = new Uri("https://xxxx.blob.core.windows.net/texts/text.txt");
var cred = new DefaultAzureCredential();
var blobClient = new BlobClient(blobUri, cred, null);
var downloadInfo = await blobClient.DownloadAsync();
using (TextReader reader = new StreamReader(downloadInfo.Value.Content))
{
string metadataBlob = await reader.ReadToEndAsync();
return metadataBlob;
}
});
Result print

Using Network Creds in dotnet core app on a mac using HttpClient

Writing a dotnet core app. I need to log in with network credentials as the service (which happens to be a TFS on-prem server) uses those to authenticate. From my (and another team members') windows machine, the following code works:
Console.WriteLine("Type in your DOMAIN password:");
var pass = GetPassword(); //command line secure string magic from SO
var networkCredential = new NetworkCredential("USERNAME", pass, "DOMAINNAME");
string tfsDefaultCollection = "https://TFSURL/DefaultCollection";
string testUrl = $"{tfsDefaultCollection}/_apis/tfvc/changesets/1234/changes?api-version=2.2";
var httpClientHandler = new HttpClientHandler
{
Credentials = networkCredential
};
var client = new HttpClient(httpClientHandler)
{
BaseAddress = new Uri(testUrl)
};
httpClientHandler.PreAuthenticate = true;
var test = client.GetAsync(testUrl).Result;
Console.WriteLine(test);
But it doesn't work from my mac. I get a 401 unauthorized. Both used the same, hardwired connection. AND this works on my mac:
curl --ntlm --user "DOMAINNAME\USERNAME" "https://TFSURL/DefaultCollection/_apis/tfvc/changesets/1234/changes?api-version=2.2"
So that rules out a connectivity question, I would think. Am I missing something I need to be doing on my mac? Can anybody point me to some documentation or way to troubleshoot what both of these requests are doing at the lowest level to see if there is a difference?
Well finally some google-foo got me there. There's a bug in dotnet core for linux/mac. This issue describes the fix:
https://github.com/dotnet/corefx/issues/25988#issuecomment-412534360
It has to do with the host machine you are connecting to uses both Kerberos and NTLM authentication methods.
Implemented below:
AppContext.SetSwitch("System.Net.Http.UseSocketsHttpHandler", false);
Console.WriteLine("Type in your DOMAIN password:");
var pass = GetPassword(); //command line secure string magic from SO
var networkCredential = new NetworkCredential("USERNAME", pass, "DOMAINNAME");
string tfsDefaultCollection = "https://TFSURL/DefaultCollection";
string testUrl = $"{tfsDefaultCollection}/_apis/tfvc/changesets/1234/changes?api-version=2.2";
var myCache = new CredentialCache
{
{
new Uri(testUrl), "NTLM",
networkCredential
}
};
var httpClientHandler = new HttpClientHandler
{
Credentials = myCache
};
var client = new HttpClient(httpClientHandler)
{
BaseAddress = new Uri(testUrl)
};
httpClientHandler.PreAuthenticate = true;
var test = client.GetAsync(testUrl).Result;
Console.WriteLine(test);
Thanks to #dmcgill50 for getting me on the right googling track.

How to implement Exchange online OAuth2.0 for unmanaged EWS API?

For managed EWS code, I have used to OAuth 2.0 to get token and it worked.
For unmanaged EWS, it is failing to connect to Exchange as an unauthorized error.
Below is the code to access unmanaged EWS.
How to make below code work with OAuth token instead of passing credentials as below?.
Binding = new ExchangeServiceBinding
{
Url = ServerUrl,
Credentials = new OAuthCredentials(token),
RequestServerVersionValue = new RequestServerVersion { Version = ExchangeVersionType.Exchange2007_SP1 },
ExchangeImpersonation = null
};
Above is not working as credential is asking of type ICredentials and it is not accepting token. Please help me.
Below is the code how I direct access managed EWS.
var authResult = await pca.AcquireTokenByUsernamePassword(ewsScopes, credential.UserName, credential.SecurePassword).ExecuteAsync();
configure the ExchangeService with the access token
ExchangeService = new ExchangeService();
ExchangeService.Url = new Uri(ServerUrl);
ExchangeService.Credentials = new OAuthCredentials(authResult.AccessToken);
One method i use (as I've never worked out how to override the WSDL classes) is if you modify the Reference.cs file that gets generated in the web references directory you can modify the GetWebResponse command (In this case the token is being passed via the credentials object password property but there a number of different approaches you can take here) eg
private String AnchorMailbox;
private bool oAuth;
protected override System.Net.WebResponse GetWebResponse(System.Net.WebRequest req)
{
if (xAnchorMailbox != null)
{
if (xAnchorMailbox != "")
{
req.Headers.Add("X-AnchorMailbox", AnchorMailbox);
}
}
if(req.Credentials is System.Net.NetworkCredential)
{
if(oAuth){
req.Headers.Add("Authorization", ("Bearer " + ((System.Net.NetworkCredential)req.Credentials).Password));
}
}
System.Net.HttpWebResponse
rep = (System.Net.HttpWebResponse)base.GetWebResponse(req);
return rep;
}

Google authentication Xamarin invalid redirect uri

I need to make authentication using Google for my app (Xamarin Android Native). I used this article. I passed step by step. Also I created client id in Google Developer Console. But I have an error. The package name is displayed on the screenshot. What can be wrong with this?
public const string RedirectUrl = "com.xplorpal.pal_:/oauth2redirect";
_auth = new OAuth2Authenticator(clientId, string.Empty, scope,
new Uri(AuthorizeUrl),
new Uri(redirectUrl),
new Uri(AccessTokenUrl),
null, IsUsingNativeUI);
I think the problem is the underscore in the redirect URL:
public const string RedirectUrl = "com.xplorpal.pal:/oauth2redirect";
_auth = new OAuth2Authenticator(clientId, string.Empty, scope,
new Uri(AuthorizeUrl),
new Uri(redirectUrl),
new Uri(AccessTokenUrl),
null, IsUsingNativeUI);
Here you can find more info about why the UriFormatException is thrown. https://msdn.microsoft.com/it-it/library/z6c2z492(v=vs.110).aspx

Google AUTH API Application Type, how important is it?

I've been doing a lot tinkering around with the authentication stuff using the .NET libraries provided by Google.
We have both a desktop and web-app side, and what we want to achieve is to authenticate ONCE, either on the desktop or the web side, and store the refresh token, and reuse it both on the web side and the desktop side.
So the situation is like so, on the desktop side, when there's no saved existing AccessToken's and RefreshToken's, we will ask the user to authenticate via this code:
using (var stream = new FileStream("client_secrets_desktop.json", FileMode.Open, FileAccess.Read))
{
credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(GoogleClientSecrets.Load(stream).Secrets,
new[] { GmailService.Scope.GmailReadonly, GmailService.Scope.GmailCompose },
"someemail#gmail.com", CancellationToken.None);
}
In this case the Client ID and Secret is of an Application type Installed Application.
On the web-application side, if there's also no refresh token yet then I'm using DotNetOpenAuth to trigger the authentication, here's the code snippet:
const string clientID = "someclientid";
const string clientSecret = "somesecret";
const string redirectUri = "http://localhost/Home/oauth2callback";
AuthorizationServerDescription server = new AuthorizationServerDescription
{
AuthorizationEndpoint = new Uri("https://accounts.google.com/o/oauth2/auth"),
TokenEndpoint = new Uri("https://accounts.google.com/o/oauth2/token"),
ProtocolVersion = ProtocolVersion.V20
};
public ActionResult AuthenticateMe()
{
List<string> scope = new List<string>
{
GmailService.Scope.GmailCompose,
GmailService.Scope.GmailReadonly,
GmailService.Scope.GmailModify
};
WebServerClient consumer = new WebServerClient(server, clientID, clientSecret);
// Here redirect to authorization site occurs
OutgoingWebResponse response = consumer.PrepareRequestUserAuthorization(
scope, new Uri(redirectUri));
response.Headers["Location"] += "&access_type=offline&approval_prompt=force";
return response.AsActionResult();
}
public void oauth2callback()
{
WebServerClient consumer = new WebServerClient(server, clientID, clientSecret);
consumer.ClientCredentialApplicator =
ClientCredentialApplicator.PostParameter(clientSecret);
IAuthorizationState grantedAccess = consumer.ProcessUserAuthorization(null);
string accessToken = grantedAccess.AccessToken;
}
Here is where I want to confirm my suspicions.
When there is a RefreshToken that exists, we use the following code snippet to call the Gmail API's
UserCredential uc = new UserCredential(flow, "someemail#gmail.com", new TokenResponse()
{
AccessToken = "lastaccesstoken",
TokenType = "Bearer",
RefreshToken = "supersecretrefreshtoken"
});
var refreshState = await uc.RefreshTokenAsync(CancellationToken.None);
var svc = new GmailService(new BaseClientService.Initializer()
{
HttpClientInitializer = uc,
ApplicationName = "Gmail Test",
});
Here's the thing I noticed is that, for me to be able to use the refresh token to refresh from either the desktop or the web side, the refresh token needs to be generated through the same client ID/secret combination. I've tested it and it seems like it's fine if we use Installed application as the application type for the Client ID for both the desktop and the web, but my question I guess is, these application type's for the client IDs, do they matter so much?
Am I doing anything wrong to do it this way?
Thanks in advance

Resources