Xamarin.Android auth AD B2C without forms. Use MSAL or AppAuth? - xamarin

I'm trying to use AD B2C with my Mobile App using Xamarin.Android, but not Forms.
All examples on how to use AD B2C with Xamarin are using the MSAL, which seems to rely on using Xamarin.Forms. I was not able to find an example or documentation on how to integrate it into Xamarin.Android.
Examples I was able to find but did not meet my needs:
https://github.com/Azure-Samples/active-directory-android-native-appauth-b2c
https://github.com/Azure-Samples/active-directory-b2c-xamarin-native
Documentation I read and tried to apply, but did not result in a succesful authentication or where not applicable to my situation:
https://developer.xamarin.com/guides/xamarin-forms/cloud-services/authentication/azure/
https://developer.xamarin.com/guides/xamarin-forms/cloud-services/authentication/azure-ad-b2c/
https://developer.xamarin.com/guides/xamarin-forms/cloud-services/authentication/azure-ad-b2c-mobile-app/
Is there a way to use MSAL in Xamarin.Android without using Forms or do I have to use some other library like AppAuth?
UPDATE:
How I tried to use MSAL:
public static string ClientId = "MyClientId";
public static string SignUpSignInPolicy = "B2C_1_MyPolicyName";
public static string Authority = "My Authority";
public static string[] Scopes = { ClientId };
private AuthenticationManager()
{
AuthenticationClient = new PublicClientApplication(ClientId, Authority);
}
public async Task<AuthenticationResult> AuthenticateAsync()
{
if (!Initialized)
throw new InvalidOperationException("Cannot authenticate before AuthenticationManager has been initialized.");
AuthResult = await AuthenticationClient.AcquireTokenAsync(Scopes,
"",
UiOptions.SelectAccount,
string.Empty,
null,
Authority,
SignUpSignInPolicy);
return AuthResult;
}
public void Initialize(Activity activity)
{
if (Initialized)
return;
// Load shared prefrences (skipped)
AuthenticationClient.PlatformParameters = new PlatformParameters(activity);
Initialized = true;
}
In the call for AquireTokenAsync(..), this results in NullPointerException:
"Value cannot be null. Parameter name: clientId"
I'm using the same parameters as in the examples (and they don't crash), everything should be initialized.
One reason for this error could be that the parameters for AquireTokenAsync(..) are different when calling it from android. (Please see the source of PublicClientApplication and look for the precompiler directives.)
But if that's the case I don't really know how to build the project with, so that the correct version for android is selected.
UPDATE2:
Added code from activity which should launch the authentication process:
public class LoginActivity : Activity
{
private async void LoginButtonClicked()
{
try
{
AuthenticationManager.AuthManager.Initialize(this);
await AuthenticationManager.AuthManager.AuthenticateAsync();
}
// Create your application here
catch (Exception e)
{
CreateAndShowDialog(e, Resources.GetString(Resource.String.error_sync));
}
}
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
base.OnActivityResult(requestCode, resultCode, data);
AuthenticationAgentContinuationHelper.SetAuthenticationAgentContinuationEventArgs(requestCode, resultCode, data);
}

Do you have this in your MainActivity.cs?
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
base.OnActivityResult(requestCode, resultCode, data);
AuthenticationAgentContinuationHelper
.SetAuthenticationAgentContinuationEventArgs(requestCode, resultCode, data);
}

This is old.
But if anyone has the same problem it's highly unlikely that he did the same stupid error as I did:
Field members are initialized in order.
There was some dependency between my field members and due to wrong order one value evaluated to null when initializing the dependent part.

Related

Store Workflow Activity Data When Publishing

I Need to store a specific activity data in another collection in database whenever a user publish a workflow in elsa.
I dont find any documentation, Please suggest me some resource or suggestion to achieve this. I have try to implement this with middleware. The Middleware code is
namespace WorkFlowV3
{
// You may need to install the Microsoft.AspNetCore.Http.Abstractions package into your project
public class CustomMiddleware
{
private readonly RequestDelegate _next;
static HttpClient client = new HttpClient();
public CustomMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext httpContext)
{
//Write Custom Logic Here....
client.BaseAddress = new Uri("#");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
string path = "/api/test-middleware-call";
HttpResponseMessage response = await client.GetAsync(path);
await _next(httpContext);
}
}
// Extension method used to add the middleware to the HTTP request pipeline.
public static class CustomMiddlewareExtensions
{
public static IApplicationBuilder UseCustomMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<CustomMiddleware>();
}
}
}
But in this process, I cant fetch the specific activity data.
The easiest way to store information in your own DB in response to the "workflow published" event is by implementing a notification handler (from MediatR) that handles the WorkflowDefinitionPublished notification.
For example:
public class MyWorkflowPublishedhandler : INotificationhandler<WorkflowDefinitionPublished>
{
private readonly IMyDatabaseStore _someRepository;
public MyWorkflowPublishedhandler(IMyDatabaseStore someRepository)
{
_someRepository = someRepository;
}
public async Task Handle(WorkflowDefinitionPublished notification, CancellationToken cancellationToken)
{
var workflowDefinition = notification.WorkflowDefinition;
// Your logic to do a thing.
}
}
To register this handler, from your Startup or Program class, add the following code:
services.AddNotificationHandler<MyWorkflowPublishedhandler>();
Your handler will be invoked every time a workflow gets published.

Has anyone successfully implemented Azure Active Directory B2C for auth using Microsoft.Identity.Client 1.1.0-preview?

I have been struggling with this for several days (three actually). I have AAD B2C working on a web app and an api. I cannot get it running on my Xamarin mobile project. I am using the UWP project to test my configuration since it has the easiest app to troubleshoot on a Windows 10 machine. I am using Visual Studio 2015 Pro.
I am using the Microsoft.Identity.Client 1.1.0-preview.
I used this as my starting point for my attempt to implement.
https://github.com/Azure-Samples/active-directory-b2c-xamarin-native
Right now the project will compile and launch. When I click on Sign in, I get a WebView, but it doesn't look exactly right....
[First Image in Screenshots]
Here are my variables...
public class Constants
{
public static string ApplicationID = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
public static string[] Scopes = {""};
public static string SignUpSignInPolicy = "B2C_1_Standard_SignUpSignIn";
public static string ResetPasswordPolicy = "B2C_1_Standard_PasswordReset";
public static string EditProfilePolicy = "B2C_1_Standard_EditProfile";
public static string Authority = "https://login.microsoftonline.com/[MyTennantName].onmicrosoft.com/B2C_1_Standard_SignUpSignIn";
public static string AuthorityEditProfile = "https://login.microsoftonline.com/[MyTennantName].onmicrosoft.com/B2C_1_Standard_EditProfile";
public static string ApiEndpoint = "https://[MyTennantName].onmicrosoft.com/apiservices";
public static UIParent UiParent = null;
}
My Login method is....
async void OnSignInSignOut(object sender, EventArgs e)
{
try
{
if (btnSignInSignOut.Text == "Sign in")
{
AuthenticationResult ar = await App.PCA.AcquireTokenAsync(Constants.Scopes, GetUserByPolicy(App.PCA.Users, Constants.SignUpSignInPolicy), Constants.UiParent);
UpdateUserInfo(ar);
UpdateSignInState(true);
}
else
{
foreach (var user in App.PCA.Users)
{
App.PCA.Remove(user);
}
UpdateSignInState(false);
}
}
catch (Exception ex)
{
// Checking the exception message
// should ONLY be done for B2C
// reset and not any other error.
if (ex.Message.Contains("AADB2C90118"))
OnPasswordReset();
// Alert if any exception excludig user cancelling sign-in dialog
else if (((ex as MsalException)?.ErrorCode != "authentication_canceled"))
await DisplayAlert($"Exception:", ex.ToString(), "Dismiss");
}
}
However before I can even enter my password I get the following....
[Second image in Screenshots]
My application definition looks like this...[Third image in screenshots]
I don't think it is recognizing my tenant and trying to log me in with a Microsoft account. I have double checked my Tenant name and Application ID.
Screenshots
I don't have enough reputation to post more than one link and one picture.
Also, the Azure AD B2C api application works for a web app. I have created a web app that can authenticate and works with the API.
It looks like while modifying the authorization value in the Sample you removed the /tfp/ part.
You should update your values as follows:
public static string Authority = "https://login.microsoftonline.com/tfp/[MyTennantName].onmicrosoft.com/B2C_1_Standard_SignUpSignIn";
public static string AuthorityEditProfile = "https://login.microsoftonline.com/tfp/[MyTennantName].onmicrosoft.com/B2C_1_Standard_EditProfile";

Alternative to HttpRequestMessage SetContext method

I have this code from codeplex
private async Task ExecuteChangeSet(
ChangeSetRequestItem changeSet,
IList<ODataBatchResponseItem> responses,
CancellationToken cancellation)
{
ChangeSetResponseItem changeSetResponse;
// Create a new ShoppingContext instance, associate it with each of the requests, start a new
// transaction, execute the changeset and then commit or rollback the transaction depending on
// whether the responses were all successful or not.
using (ShoppingContext context = new ShoppingContext())
{
foreach (HttpRequestMessage request in changeSet.Requests)
{
request.SetContext(context);
}
The complete sample code can be found here.
I downloaded the project and it is using .net framework 4.5
but in .NET Framework 4.6.1 the SetContext method is no longer present
I want to know how can I achieve the same in framework version 4.6.1?
I am basically creating a OData V3 Service which will be hosted in IIS.
You can go with creating your own functionality for setting the context and retrieve it where needed, with HttpRequestMessage extensions like:
Example class:
public static class HttpRequestMessageExtensions
{
private const string Context = "ShoppingContext";
public static void SetContext(this HttpRequestMessage request, ShoppingContext context)
{
request.Properties[Context] = context;
}
public static ShoppingContext GetContext(this HttpRequestMessage request)
{
object context;
if (request.Properties.TryGetValue(Context, out context))
{
return (ShoppingContext) context;
}
return null;
}
}
Usage:
//Setting context
request.SetContext(context);
//reading context
var context = request.GetContext();

Global exception handling in OWIN middleware

I'm trying to create a unified error handling/reporting in ASP.NET Web API 2.1 Project built on top of OWIN middleware (IIS HOST using Owin.Host.SystemWeb).
Currently I used a custom exception logger which inherits from System.Web.Http.ExceptionHandling.ExceptionLogger and uses NLog to log all exceptions as the code below:
public class NLogExceptionLogger : ExceptionLogger
{
private static readonly Logger Nlog = LogManager.GetCurrentClassLogger();
public override void Log(ExceptionLoggerContext context)
{
//Log using NLog
}
}
I want to change the response body for all API exceptions to a friendly unified response which hides all exception details using System.Web.Http.ExceptionHandling.ExceptionHandler as the code below:
public class ContentNegotiatedExceptionHandler : ExceptionHandler
{
public override void Handle(ExceptionHandlerContext context)
{
var errorDataModel = new ErrorDataModel
{
Message = "Internal server error occurred, error has been reported!",
Details = context.Exception.Message,
ErrorReference = context.Exception.Data["ErrorReference"] != null ? context.Exception.Data["ErrorReference"].ToString() : string.Empty,
DateTime = DateTime.UtcNow
};
var response = context.Request.CreateResponse(HttpStatusCode.InternalServerError, errorDataModel);
context.Result = new ResponseMessageResult(response);
}
}
And this will return the response below for the client when an exception happens:
{
"Message": "Internal server error occurred, error has been reported!",
"Details": "Ooops!",
"ErrorReference": "56627a45d23732d2",
"DateTime": "2015-12-27T09:42:40.2982314Z"
}
Now this is working all great if any exception occurs within an Api Controller request pipeline.
But in my situation I'm using the middleware Microsoft.Owin.Security.OAuth for generating bearer tokens, and this middleware doesn't know anything about Web API exception handling, so for example if an exception has been in thrown in method ValidateClientAuthentication my NLogExceptionLogger not ContentNegotiatedExceptionHandler will know anything about this exception nor try to handle it, the sample code I used in the AuthorizationServerProvider is as the below:
public class AuthorizationServerProvider : OAuthAuthorizationServerProvider
{
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
//Expcetion occurred here
int x = int.Parse("");
context.Validated();
return Task.FromResult<object>(null);
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
if (context.UserName != context.Password)
{
context.SetError("invalid_credentials", "The user name or password is incorrect.");
return;
}
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim(ClaimTypes.Name, context.UserName));
context.Validated(identity);
}
}
So I will appreciate any guidance in implementing the below 2 issues:
1 - Create a global exception handler which handles only exceptions generated by OWIN middle wares? I followed this answer and created a middleware for exception handling purposes and registered it as the first one and I was able to log exceptions originated from "OAuthAuthorizationServerProvider", but I'm not sure if this is the optimal way to do it.
2 - Now when I implemented the logging as the in the previous step, I really have no idea how to change the response of the exception as I need to return to the client a standard JSON model for any exception happening in the "OAuthAuthorizationServerProvider". There is a related answer here I tried to depend on but it didn't work.
Here is my Startup class and the custom GlobalExceptionMiddleware I created for exception catching/logging. The missing peace is returning a unified JSON response for any exception. Any ideas will be appreciated.
public class Startup
{
public void Configuration(IAppBuilder app)
{
var httpConfig = new HttpConfiguration();
httpConfig.MapHttpAttributeRoutes();
httpConfig.Services.Replace(typeof(IExceptionHandler), new ContentNegotiatedExceptionHandler());
httpConfig.Services.Add(typeof(IExceptionLogger), new NLogExceptionLogger());
OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = new AuthorizationServerProvider()
};
app.Use<GlobalExceptionMiddleware>();
app.UseOAuthAuthorizationServer(OAuthServerOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
app.UseWebApi(httpConfig);
}
}
public class GlobalExceptionMiddleware : OwinMiddleware
{
public GlobalExceptionMiddleware(OwinMiddleware next)
: base(next)
{ }
public override async Task Invoke(IOwinContext context)
{
try
{
await Next.Invoke(context);
}
catch (Exception ex)
{
NLogLogger.LogError(ex, context);
}
}
}
Ok, so this was easier than anticipated, thanks for #Khalid for the heads up, I have ended up creating an owin middleware named OwinExceptionHandlerMiddleware which is dedicated for handling any exception happening in any Owin Middleware (logging it and manipulating the response before returning it to the client).
You need to register this middleware as the first one in the Startup class as the below:
public class Startup
{
public void Configuration(IAppBuilder app)
{
var httpConfig = new HttpConfiguration();
httpConfig.MapHttpAttributeRoutes();
httpConfig.Services.Replace(typeof(IExceptionHandler), new ContentNegotiatedExceptionHandler());
httpConfig.Services.Add(typeof(IExceptionLogger), new NLogExceptionLogger());
OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = new AuthorizationServerProvider()
};
//Should be the first handler to handle any exception happening in OWIN middlewares
app.UseOwinExceptionHandler();
// Token Generation
app.UseOAuthAuthorizationServer(OAuthServerOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
app.UseWebApi(httpConfig);
}
}
And the code used in the OwinExceptionHandlerMiddleware as the below:
using AppFunc = Func<IDictionary<string, object>, Task>;
public class OwinExceptionHandlerMiddleware
{
private readonly AppFunc _next;
public OwinExceptionHandlerMiddleware(AppFunc next)
{
if (next == null)
{
throw new ArgumentNullException("next");
}
_next = next;
}
public async Task Invoke(IDictionary<string, object> environment)
{
try
{
await _next(environment);
}
catch (Exception ex)
{
try
{
var owinContext = new OwinContext(environment);
NLogLogger.LogError(ex, owinContext);
HandleException(ex, owinContext);
return;
}
catch (Exception)
{
// If there's a Exception while generating the error page, re-throw the original exception.
}
throw;
}
}
private void HandleException(Exception ex, IOwinContext context)
{
var request = context.Request;
//Build a model to represet the error for the client
var errorDataModel = NLogLogger.BuildErrorDataModel(ex);
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
context.Response.ReasonPhrase = "Internal Server Error";
context.Response.ContentType = "application/json";
context.Response.Write(JsonConvert.SerializeObject(errorDataModel));
}
}
public static class OwinExceptionHandlerMiddlewareAppBuilderExtensions
{
public static void UseOwinExceptionHandler(this IAppBuilder app)
{
app.Use<OwinExceptionHandlerMiddleware>();
}
}
There are a few ways to do what you want:
Create middleware that is registered first, then all exceptions will bubble up to that middleware. At this point just write out your JSON out via the Response object via the OWIN context.
You can also create a wrapping middleware which wraps the Oauth middleware. In this case it will on capture errors originating from this specific code path.
Ultimately writing your JSON message is about creating it, serializing it, and writing it to the Response via the OWIN context.
It seems like you are on the right path with #1. Hope this helps, and good luck :)
The accepted answer is unnecessarily complex and doesn't inherit from OwinMiddleware class
All you need to do is this:
public class HttpLogger : OwinMiddleware
{
public HttpLogger(OwinMiddleware next) : base(next) { }
public override async Task Invoke(IOwinContext context)
{
await Next.Invoke(context);
Log(context)
}
}
Also, no need to create extension method.. it is simple enough to reference without
appBuilder.Use(typeof(HttpErrorLogger));
And if you wanna log only specific requests, you can filter on context properties:
ex:
if (context.Response.StatusCode != 200) { Log(context) }

Sterling database not persist on Windows phone

I followed sterling database examples from several persons. Neither of them seems to work out for me. When I persist some stuff on my database everything clearly gets persisted using sterling (on my phone, not emulator) when debugging. However when I relaunch my app the database is empty. Is somebody else experiencing the same problem. Or does someone have a complete working example. I know my serializing and saving works... As long as I don't relaunch my app loading my state works...
Code in my app.cs
public static ISterlingDatabaseInstance Database { get; private set; }
private static SterlingEngine _engine;
private static SterlingDefaultLogger _logger;
private void Application_Launching(object sender, LaunchingEventArgs e)
{
ActivateEngine();
}
// Code to execute when the application is activated (brought to foreground)
// This code will not execute when the application is first launched
private void Application_Activated(object sender, ActivatedEventArgs e)
{
ActivateEngine();
}
// Code to execute when the application is deactivated (sent to background)
// This code will not execute when the application is closing
private void Application_Deactivated(object sender, DeactivatedEventArgs e)
{
DeactivateEngine();
}
// Code to execute when the application is closing (eg, user hit Back)
// This code will not execute when the application is deactivated
private void Application_Closing(object sender, ClosingEventArgs e)
{
DeactivateEngine();
}
private void ActivateEngine()
{
_engine = new SterlingEngine();
_logger = new SterlingDefaultLogger(SterlingLogLevel.Information);
_engine.Activate();
Database = _engine.SterlingDatabase.RegisterDatabase<SokobanDb>();
}
private void DeactivateEngine()
{
_logger.Detach();
_engine.Dispose();
Database = null;
_engine = null;
}
Code in my viewModel
public void LoadState(int level)
{
var levelState = App.Database.Load<LevelState>(level);
if (levelState != null)
{
//TODO: check if game started, then create board from boardstring property else create new board
//Labyrint = new Labyrint(Factory.CreateBoard());
NewGame(level);
}
else
{
NewGame(level);
}
}
public void SaveState()
{
var levelState = new LevelState { LevelId = _level, Moves = Labyrint.Moves, Board = Labyrint.ToString() };
App.Database.Save(levelState);
App.Database.Flush(); //Required to clean indexes etc.
}
The default Sterling database uses an in-memory driver. To persist, pass it an isolated storage driver. Per the documentation guide quickstart:
https://sites.google.com/site/sterlingdatabase/sterling-user-guide/getting-started
The code looks like this:
_databaseInstance = _engine.SterlingDatabase.RegisterDatabase(new IsolatedStorageDriver());
Note the instance of the isolated storage driver being passed in. That should do it for you.
When in doubt, take a look at the unit tests shipped with the source. Those contain tons of examples of memory, isolated storage, etc. to show various patterns for setting it up.

Resources