Xamarin: Accessing values inside local functions - ios

I'm working on integrating Firebase Auth into my app. I've had to set up an interface to use platform-specific code (iOS and Android) to create users. My interface method in iOS "CreateNewUser" calls the "Auth.DefaultInstance.CreateUser" method that creates a new user. How can I get the "userID" variable from within the local function be the return value for the greater "CreateNewUser" function?
public string CreateNewUser(string email, string password)
{
Console.WriteLine("hhhhh");
string userId = "";
Auth.DefaultInstance.CreateUser(email, password,(authResult, error) => {
Console.WriteLine("idd is " + authResult.User.Uid);
userId = authResult.User.Uid;
});
return userId;
}

I usually use a TaskCompletionSource to get it to work.
public Task<string> CreateNewUser(string email, string password)
{
TaskCompletionSource<string> tcs = new TaskCompletionSource<string>();
Auth.DefaultInstance.CreateUser(email, password,(authResult, error) => {
Console.WriteLine("idd is " + authResult.User.Uid);
tcs.SetResult(authResult.User.Uid);
});
return tcs.Task;
}
You can then just do await CreateNewUser("bla","bla");

Related

Which PayPal Payment method should I use?

I have an application of my client in which he has used PaypalAdaptivePayments but the API stopped working since 2 years back, now this project is within my hands so I started investigating the API and found that PayPal has deprecated the usage of this API, actually in his application what he tends to have is this:
1- There is only 1 account of this Vendor in PayPal in which the amount debits and credits from the application.
2- Basically this application is sort of Ride booking web app, in which the customer books a ride and then deposits X amount into the wallet (remember this wallet is connected to the vendor account I have mentioned in point 1).
3- When the customer ride is completed he marks the amount as to be cleared for DEBIT, then this decision is saved into my database but till this stage the driver is not reimbursed.
4- The ADMIN logs in to this site and then he goes to the drivers list and then select an appropriate driver and then he puts in his X COMMISSION for this ride, and then clicks on PAY so with this action the driver gets paid. Note: This is a commission based procedure so for example the RIDE which an customer has booked was of USD100 so he credited this amount into the VENDORS wallet, then when the VENDOR is about to lend payment to the DRIVER he enters his own commission for e.g. 10%, so the DRIVER would be paid USD90 only. This payment is also deducted from the VENDORS wallet and then transferred to the DRIVER.
Now after painting the scenario, can you please guide me which API is best suited for this scenario? as there are LOADS of PayPal API and SDK .... I am totally lost in their world, Please keep in mind my application is build in ASP.NET MVC.
Please note:
My Client(Vendor) already owns a SANDBOX and a verified BUSINESS ACCOUNT in PayPal.
Warm Regards.
Emad.
For community ease I am sharing my code:
using Classes;
using EFare.Classes;
using PayPalMvc;
using PayPalMvc.Enums;
using SampleMVC3WebApplication.Models;
using SampleMVC3WebApplication.Services;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Web;
using System.Web.Mvc;
namespace SampleMVC3WebApplication
{
public class WebUILogging
{
// Add your favourite logger here
public static void LogMessage(string message)
{
DoTrace(message);
}
public static void LogLongMessage(string message, string longMessage)
{
DoTrace(message);
DoTrace(longMessage);
}
public static void LogException(string message, Exception ex)
{
DoTrace(message);
DoTrace(ex.Message);
DoTrace(ex.StackTrace);
}
private static void DoTrace(string message)
{
Trace.WriteLine(DateTime.Now + " - " + message);
}
}
}
namespace SampleMVC3WebApplication.Services
{
public interface ITransactionService
{
SetExpressCheckoutResponse SendPayPalSetExpressCheckoutRequest(ApplicationCart cart, string serverURL,
string userEmail = null);
GetExpressCheckoutDetailsResponse SendPayPalGetExpressCheckoutDetailsRequest(string token);
DoExpressCheckoutPaymentResponse SendPayPalDoExpressCheckoutPaymentRequest(ApplicationCart cart, string token,
string payerId);
}
/// <summary>
/// The Transaction Service is used to transform a purchase object (eg cart, basket, or single item) into a sale
/// request with PayPal (in this case a cart)
/// It also allows your app to store the transactions in your database (create a table to match the PayPalTransaction
/// model)
/// You should copy this file into your project and modify it to accept your purchase object, store PayPal transaction
/// responses in your database,
/// as well as log events with your favourite logger.
/// </summary>
public class TransactionService : ITransactionService
{
private readonly ITransactionRegistrar _payPalTransactionRegistrar = new TransactionRegistrar();
public SetExpressCheckoutResponse SendPayPalSetExpressCheckoutRequest(ApplicationCart cart, string serverURL,
string userEmail = null)
{
try
{
WebUILogging.LogMessage("SendPayPalSetExpressCheckoutRequest");
// Optional handling of cart items: If there is only a single item being sold we don't need a list of expressCheckoutItems
// However if you're selling a single item as a sale consider also adding it as an ExpressCheckoutItem as it looks better once you get to PayPal's site
// Note: ExpressCheckoutItems are currently NOT stored by PayPal against the sale in the users order history so you need to keep your own records of what items were in a cart
List<ExpressCheckoutItem> expressCheckoutItems = null;
if (cart.Items != null)
{
expressCheckoutItems = new List<ExpressCheckoutItem>();
foreach (var item in cart.Items)
expressCheckoutItems.Add(new ExpressCheckoutItem(item.Quantity, item.Price, item.Name,
item.Description));
}
var response = _payPalTransactionRegistrar.SendSetExpressCheckout(cart.Currency, cart.TotalPrice,
cart.PurchaseDescription, cart.Id.ToString(), serverURL, expressCheckoutItems, userEmail);
// Add a PayPal transaction record
var transaction = new PayPalTransaction
{
RequestId = response.RequestId,
TrackingReference = cart.Id.ToString(),
RequestTime = DateTime.Now,
RequestStatus = response.ResponseStatus.ToString(),
TimeStamp = response.TIMESTAMP,
RequestError = response.ErrorToString,
Token = response.TOKEN
};
// Store this transaction in your Database
return response;
}
catch (Exception ex)
{
WebUILogging.LogException(ex.Message, ex);
}
return null;
}
public GetExpressCheckoutDetailsResponse SendPayPalGetExpressCheckoutDetailsRequest(string token)
{
try
{
WebUILogging.LogMessage("SendPayPalGetExpressCheckoutDetailsRequest");
var response = _payPalTransactionRegistrar.SendGetExpressCheckoutDetails(token);
// Add a PayPal transaction record
var transaction = new PayPalTransaction
{
RequestId = response.RequestId,
TrackingReference = response.TrackingReference,
RequestTime = DateTime.Now,
RequestStatus = response.ResponseStatus.ToString(),
TimeStamp = response.TIMESTAMP,
RequestError = response.ErrorToString,
Token = response.TOKEN,
PayerId = response.PAYERID,
RequestData = response.ToString
};
// Store this transaction in your Database
return response;
}
catch (Exception ex)
{
WebUILogging.LogException(ex.Message, ex);
}
return null;
}
public DoExpressCheckoutPaymentResponse SendPayPalDoExpressCheckoutPaymentRequest(ApplicationCart cart,
string token, string payerId)
{
try
{
WebUILogging.LogMessage("SendPayPalDoExpressCheckoutPaymentRequest");
var response =
_payPalTransactionRegistrar.SendDoExpressCheckoutPayment(token, payerId, cart.Currency,
cart.TotalPrice);
// Add a PayPal transaction record
var transaction = new PayPalTransaction
{
RequestId = response.RequestId,
TrackingReference = cart.Id.ToString(),
RequestTime = DateTime.Now,
RequestStatus = response.ResponseStatus.ToString(),
TimeStamp = response.TIMESTAMP,
RequestError = response.ErrorToString,
Token = response.TOKEN,
RequestData = response.ToString,
PaymentTransactionId = response.PaymentTransactionId,
PaymentError = response.PaymentErrorToString
};
// Store this transaction in your Database
return response;
}
catch (Exception ex)
{
WebUILogging.LogException(ex.Message, ex);
}
return null;
}
}
}
namespace SampleMVC3WebApplication.Controllers
{
public class PurchaseController : Controller
{
private readonly TransactionService transactionService = new TransactionService();
private bool checkcustomerid(string uid)
{
var dl = new AccountDataLayer();
var ds = dl.Inline_Process("select UserId from dbo.Login_Table where UserId='" + uid +
"' and UType='customer'");
return ds.Tables[0].Rows.Count > 0;
}
#region Set Express Checkout and Get Checkout Details
public ActionResult PayPalExpressCheckout()
{
WebUILogging.LogMessage("Express Checkout Initiated");
// SetExpressCheckout
var cart = (ApplicationCart)Session["Cart"];
var serverURL = HttpContext.Request.Url.GetLeftPart(UriPartial.Authority) +
VirtualPathUtility.ToAbsolute("~/");
var transactionResponse =
transactionService.SendPayPalSetExpressCheckoutRequest(cart, serverURL);
// If Success redirect to PayPal for user to make payment
if (transactionResponse == null || transactionResponse.ResponseStatus != ResponseType.Success)
{
SetUserNotification(
"Sorry there was a problem with initiating a PayPal transaction. Please try again and contact an Administrator if this still doesn't work.");
var errorMessage = transactionResponse == null
? "Null Transaction Response"
: transactionResponse.ErrorToString;
WebUILogging.LogMessage(
"Error initiating PayPal SetExpressCheckout transaction. Error: " + errorMessage);
return RedirectToAction("Error", "Purchase");
}
return Redirect(string.Format(PayPalMvc.Configuration.Current.PayPalRedirectUrl,
transactionResponse.TOKEN));
}
public ActionResult
PayPalExpressCheckoutAuthorisedSuccess(string token,
string PayerID) // Note "PayerID" is returned with capitalisation as written
{
// PayPal redirects back to here
WebUILogging.LogMessage("Express Checkout Authorised");
// GetExpressCheckoutDetails
TempData["token"] = token;
TempData["payerId"] = PayerID;
var transactionResponse =
transactionService.SendPayPalGetExpressCheckoutDetailsRequest(token);
if (transactionResponse == null || transactionResponse.ResponseStatus != ResponseType.Success)
{
SetUserNotification(
"Sorry there was a problem with initiating a PayPal transaction. Please try again and contact an Administrator if this still doesn't work.");
var errorMessage = transactionResponse == null
? "Null Transaction Response"
: transactionResponse.ErrorToString;
WebUILogging.LogMessage("Error initiating PayPal GetExpressCheckoutDetails transaction. Error: " +
errorMessage);
return RedirectToAction("Error", "Purchase");
}
return RedirectToAction("ConfirmPayPalPayment");
}
#endregion Set Express Checkout and Get Checkout Details
#region Confirm Payment
public ActionResult ConfirmPayPalPayment()
{
WebUILogging.LogMessage("Express Checkout Confirmation");
var cart = (ApplicationCart)Session["Cart"];
return View(cart);
}
[HttpPost]
public ActionResult ConfirmPayPalPayment(bool confirmed = true)
{
WebUILogging.LogMessage("Express Checkout Confirmed");
var cart = (ApplicationCart)Session["Cart"];
// DoExpressCheckoutPayment
var token = TempData["token"].ToString();
var payerId = TempData["payerId"].ToString();
var transactionResponse =
transactionService.SendPayPalDoExpressCheckoutPaymentRequest(cart, token, payerId);
if (transactionResponse == null || transactionResponse.ResponseStatus != ResponseType.Success)
{
if (transactionResponse != null && transactionResponse.L_ERRORCODE0 == "10486")
{ // Redirect user back to PayPal in case of Error 10486 (bad funding method)
// https://www.x.com/developers/paypal/documentation-tools/how-to-guides/how-to-recover-funding-failure-error-code-10486-doexpresscheckout
WebUILogging.LogMessage("Redirecting User back to PayPal due to 10486 error (bad funding method - typically an invalid or maxed out credit card)");
return Redirect(string.Format(PayPalMvc.Configuration.Current.PayPalRedirectUrl, token));
}
else
{
SetUserNotification(
"Sorry there was a problem with taking the PayPal payment, so no money has been transferred. Please try again and contact an Administrator if this still doesn't work.");
var errorMessage = transactionResponse == null
? "Null Transaction Response"
: transactionResponse.ErrorToString;
WebUILogging.LogMessage("Error initiating PayPal DoExpressCheckoutPayment transaction. Error: " +
errorMessage);
return RedirectToAction("Error", "Purchase");
}
}
if (transactionResponse.PaymentStatus == PaymentStatus.Completed)
return RedirectToAction("PostPaymentSuccess");
// Something went wrong or the payment isn't complete
WebUILogging.LogMessage("Error taking PayPal payment. Error: " + transactionResponse.ErrorToString +
" - Payment Error: " + transactionResponse.PaymentErrorToString);
TempData["TransactionResult"] = transactionResponse.PAYMENTREQUEST_0_LONGMESSAGE;
return RedirectToAction("PostPaymentFailure");
}
#endregion Confirm Payment
#region Post Payment and Cancellation
public ActionResult PostPaymentSuccess()
{
WebUILogging.LogMessage("Post Payment Result: Success");
var cart = (ApplicationCart)Session["Cart"];
ViewBag.TrackingReference = cart.Id;
ViewBag.Description = cart.PurchaseDescription;
ViewBag.TotalCost = cart.TotalPrice;
ViewBag.Currency = cart.Currency;
var dl = new Customer();
var amt = "";
var date = "";
;
var time = "";
var EFareloginCookie = Request.Cookies["Efarelogin_Cookies"];
if (EFareloginCookie != null)
if (checkcustomerid(EFareloginCookie["UserId"]))
{
amt = cart.TotalPrice.ToString();
date = DateTime.Now.ToString("yyyy-MM-dd");
time = DateTime.Now.ToString("hh:mm:ss");
var i = dl.addMoney(EFareloginCookie["UserId"], amt, date, time);
if (i > 0)
{
TempData["WalletSuccess"] = "Data saved successfully.";
//return RedirectToAction("Wallet", "Account");
ModelState.Clear();
}
else
{
TempData["Walleterror"] = "Opps something is wrong.";
}
}
return View();
}
public ActionResult PostPaymentFailure()
{
WebUILogging.LogMessage("Post Payment Result: Failure");
ViewBag.ErrorMessage = TempData["TransactionResult"];
return View();
}
public ActionResult CancelPayPalTransaction()
{
return View();
}
#endregion Post Payment and Cancellation
#region Transaction Error
private void SetUserNotification(string notification)
{
TempData["ErrorMessage"] = notification;
}
public ActionResult Error()
{
ViewBag.ErrorMessage = TempData["ErrorMessage"];
return View();
}
#endregion Transaction Error
}
}
Use PayPal Checkout for receiving money: https://developer.paypal.com/docs/checkout/ --- for a server-based API integration see the front-end UI at https://developer.paypal.com/demo/checkout/#/pattern/server
Request access to Payouts for sending money: https://developer.paypal.com/docs/payouts/integrate/prerequisites/#get-access-to-paypal-payouts

Connect to Office365 via backend service using OAuth2 in NON interactive way (bar initial setup)

I have a background service which reads & sends from a mailbox. It is created in a web ui, but after the schedule is created and mailbox set, it should run automatically, without further user prompt.
I have used the various combinations of the MSAL and both public and confidential clients (either would be acceptable as the server can maintain the client secret.
I have used the EWS client and got that working, but there is a note that the client_credentials flow won't work for IMAP/POP/SMTP.
I have a small console app working, but each time it runs, it needs to login interactively, and so long as I don't restart the application, it will keep authenticating, and I can call the AquireTokenSilently.
The Question
How can I make the MSAL save the tokens/data such that when it next runs, I can authenticate without user interaction again? I can store whatever is needed to make this work when the user authenticates, but I don't know what that should be nor how to reinstate it to make a new request, if the console app is restarted.
The Code
internal async Task<string> Test()
{
PublicClientApplication =
PublicClientApplicationBuilder.Create( "5896de31-e251-460c-9dc2-xxxxxxxxxxxx" )
.WithRedirectUri( "https://login.microsoftonline.com/common/oauth2/nativeclient" )
.WithAuthority( AzureCloudInstance.AzurePublic, ConfigurationManager.AppSettings["tenantId"] )
.Build();
//var scopes = new string[] { "email", "offline_access", "profile", "User.Read", "Mail.Read" };
var scopes = new string[] { "https://outlook.office.com/IMAP.AccessAsUser.All" };
var accounts = await PublicClientApplication.GetAccountsAsync();
var firstAccount = accounts.FirstOrDefault();
AuthenticationResult authResult;
if (firstAccount == null )
{
authResult = await PublicClientApplication.AcquireTokenInteractive( scopes ).ExecuteAsync();
}
else
{
//The firstAccount is null when the console app is run again
authResult = await PublicClientApplication.AcquireTokenSilent( scopes, firstAccount ).ExecuteAsync();
}
if(authResult == null)
{
authResult = await PublicClientApplication.AcquireTokenInteractive( scopes ).ExecuteAsync();
}
MailBee.Global.LicenseKey = "MN120-569E9E8D9E5B9E8D9EC8C4BC83D3-D428"; // (demo licence only)
MailBee.ImapMail.Imap imap = new MailBee.ImapMail.Imap();
var xOAuthkey = MailBee.OAuth2.GetXOAuthKeyStatic( authResult.Account.Username, authResult.AccessToken );
imap.Connect( "imap.outlook.com", 993 );
imap.Login( null, xOAuthkey, AuthenticationMethods.SaslOAuth2, AuthenticationOptions.None, null );
imap.SelectFolder( "INBOX" );
var count = imap.MessageCount.ToString();
return authResult.AccessToken;
}
It feels very much like a step missed, which can store the information to make subsequent requests and I would love a pointer in the right direction please.
When you create your PublicClientApplication, it provides you with the UserTokenCache.
UserTokenCache implements interface ITokenCache, which defines events to subscribe to token cache serialization requests as well as methods to serialize or de-serialize the cache at various formats.
You should create your own TokenCacheBuilder, which can store the tokens in file/memory/database etc.. and then use the events to subscribe to to token cache request.
An example of a FileTokenCacheProvider:
public abstract class MsalTokenCacheProviderBase
{
private Microsoft.Identity.Client.ITokenCache cache;
private bool initialized = false;
public MsalTokenCacheProviderBase()
{
}
public void InitializeCache(Microsoft.Identity.Client.ITokenCache tokenCache)
{
if (initialized)
return;
cache = tokenCache;
cache.SetBeforeAccessAsync(OnBeforeAccessAsync);
cache.SetAfterAccessAsync(OnAfterAccessAsync);
initialized = true;
}
private async Task OnAfterAccessAsync(TokenCacheNotificationArgs args)
{
if (args.HasStateChanged)
{
if (args.HasTokens)
{
await StoreAsync(args.Account.HomeAccountId.Identifier,
args.TokenCache.SerializeMsalV3()).ConfigureAwait(false);
}
else
{
// No token in the cache. we can remove the cache entry
await DeleteAsync<bool>(args.SuggestedCacheKey).ConfigureAwait(false);
}
}
}
private async Task OnBeforeAccessAsync(TokenCacheNotificationArgs args)
{
if (!string.IsNullOrEmpty(args.SuggestedCacheKey))
{
byte[] tokenCacheBytes = await GetAsync<byte[]>(args.SuggestedCacheKey).ConfigureAwait(false);
args.TokenCache.DeserializeMsalV3(tokenCacheBytes, shouldClearExistingCache: true);
}
}
protected virtual Task OnBeforeWriteAsync(TokenCacheNotificationArgs args)
{
return Task.CompletedTask;
}
public abstract Task StoreAsync<T>(string key, T value);
public abstract Task DeleteAsync<T>(string key);
public abstract Task<T> GetAsync<T>(string key);
public abstract Task ClearAsync();
}
And the MsalFileTokenCacheProvider:
public sealed class MsalFileTokenCacheProvider : MsalTokenCacheProviderBase
{
private string basePath;
public MsalFileTokenCacheProvider(string basePath)
{
this.basePath = basePath;
}
public override Task ClearAsync()
{
throw new NotImplementedException();
}
public override Task DeleteAsync<T>(string key)
{
if (string.IsNullOrEmpty(key))
{
throw new ArgumentException("Key MUST have a value");
}
string path = Path.Combine(basePath, key + ".json");
if (File.Exists(path))
File.Delete(path);
return Task.FromResult(true);
}
public override Task<T> GetAsync<T>(string key)
{
if (string.IsNullOrEmpty(key))
{
throw new ArgumentException("Key MUST have a value");
}
string path = Path.Combine(basePath, key + ".json");
if (File.Exists(path))
{
T value = JsonConvert.DeserializeObject<T>(File.ReadAllText(path));
return Task.FromResult(value);
}
else
return Task.FromResult(default(T));
}
public override Task StoreAsync<T>(string key, T value)
{
string contents = JsonConvert.SerializeObject(value);
string path = Path.Combine(basePath, key + ".json");
File.WriteAllText(path, contents);
return Task.FromResult(value);
}
}
So based on your code you will have:
PublicClientApplication =
PublicClientApplicationBuilder.Create( "5896de31-e251-460c-9dc2-xxxxxxxxxxxx" )
.WithRedirectUri( "https://login.microsoftonline.com/common/oauth2/nativeclient" )
.WithAuthority( AzureCloudInstance.AzurePublic, ConfigurationManager.AppSettings["tenantId"] )
.Build();
MsalFileTokenCacheProvider cacheProvider = new MsalFileTokenCacheProvider("TokensFolder");
cacheProvider.InitializeCache(PublicClientApplication.UserTokenCache);
//var scopes = new string[] { "email", "offline_access", "profile", "User.Read", "Mail.Read" };
var scopes = new string[] { "https://outlook.office.com/IMAP.AccessAsUser.All" };
// when you call the below code, the PublicClientApplication will use your token cache
//provider in order to get the required Account. You should also use the
//PublicClientApplication.GetAccountAsync(key) which will use the token cache provider for
//the specific account that you want to get the token. If there is an account you could
//just call the AcquireTokenSilent method. The acquireTokenSilent method will take care of the token expiration and will refresh if needed.
//Please bare in mind that in some circumstances the AcquireTokenSilent method will fail and you will have to use the AcquireTokenInteractive method again. //Example of this would be when the user changes password, or has removed the access to your Application via their Account.
var accounts = await PublicClientApplication.GetAccountsAsync();
var firstAccount = accounts.FirstOrDefault();
Please refer to the following documentation from Microsoft.
https://learn.microsoft.com/en-us/azure/active-directory/develop/msal-net-token-cache-serialization

Signalr .Net Client Console application receive messages from hub only once

I'm using Signalr .Net Client in my Console application to receive messages from the Signalr Hub which is a separate web application.
My console application is connecting to the hub correctly and receive message from the hub only once. Then the client method in the Signalr .Net client not getting called.
Once I stop the console application and run it, again it receive a message from the hub only once and nothing happens.
Here is my Hub Code
public override Task OnConnected()
{
try
{
var cType = Context.QueryString["type"];
var connectionId = Context.ConnectionId;
var connectedUserList = (from d in Users
where d.ClientType == cType
select d).ToList();
if (connectedUserList.Count > 0)
{
var conUser = connectedUserList.First<ConnectedUsers>();
conUser.ConnectionIds.Add(connectionId);
}
else
{
var newUser = new ConnectedUsers
{
ConnectionIds = new HashSet<string> {connectionId}
,
ClientType = cType
};
Users.Add(newUser);
}
}
catch (Exception ex)
{
).Error(ex);
}
return base.OnConnected();
}
And My .Net Client Connection
static void Main(string[] args)
{
SignalrHandler();
Console.ReadLine();
}
public static async void SignalrHandler()
{
var url = ConfigurationSettings.AppSettings["Url"] ?? #"http://localhost:1010/";
var querystringData = new Dictionary<string, string> { { "type", "WIN" } };
_hubConnection = new HubConnection(url, querystringData);
MarcolinMainProxy = _hubConnection.CreateHubProxy("MainHub");
MarcolinMainProxy.On<string>("sendAlert", type => InvokeMethod(type));
await _hubConnection.Start();
}
Client Method
private static void InvokeMethod(string type)
{
Console.WriteLine(String.Format("Recieved Message From Server On :{0}",System.DateTime.Now.ToString()));
Console.WriteLine("Message Received");
Console.ReadLine();
}
And This happens when I use an Invoke method with following line
MarcolinMainProxy.On<string>("sendAlert", type => InvokeMethod(type));
And when I use following line it works..
MarcolinMainProxy.On<string>("sendAlert", stock => Console.WriteLine("Symbol {0} Price {1}", "sd", "sdde"));
Check the following link
https://damienbod.com/2013/11/13/signalr-messaging-a-complete-client-with-a-console-application/
You have to change your code to
MarcolinMainProxy.On<string>("sendAlert", InvokeMethod);

Passing parameters to Azure Active Directory authentication

I have an ASP.Net MVC Application, Owin, and I'm using Azure Active Directory authentication as well.
I want to pass a parameter when the user is redirected to the Azure AD authentication page. So when the user signs in or signs up I want to pass ProjectId (int) as a parameter.
After the user signs in/up and is redirected to my Application I want to receive the ProjectId I passed as a parameter.
How can I achieve that?
edit: Adding code
// The ACR claim is used to indicate which policy was executed
public const string AcrClaimType = "http://schemas.microsoft.com/claims/authnclassreference";
public const string PolicyKey = "b2cpolicy";
private const string OidcMetadataSuffix = "/.well-known/openid-configuration";
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
OpenIdConnectAuthenticationOptions options = new OpenIdConnectAuthenticationOptions
{
// These are standard OpenID Connect parameters, with values pulled from web.config
ClientId = ClientId,
RedirectUri = RedirectUri,
PostLogoutRedirectUri = RedirectUri,
UseTokenLifetime = false,
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = AuthenticationFailed,
RedirectToIdentityProvider = OnRedirectToIdentityProvider,
SecurityTokenValidated = OnSecurityTokenValidated
},
Scope = "openid",
ResponseType = "id_token",
// The PolicyConfigurationManager takes care of getting the correct Azure AD authentication
// endpoints from the OpenID Connect metadata endpoint. It is included in the PolicyAuthHelpers folder.
ConfigurationManager = new PolicyConfigurationManager(
string.Format(CultureInfo.InvariantCulture, AadInstance, Tenant, "/v2.0", OidcMetadataSuffix),
new[] { SignUpPolicyId, SignInPolicyId, ProfilePolicyId }),
// This piece is optional - it is used for displaying the user's name in the navigation bar.
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name"
}
};
app.UseOpenIdConnectAuthentication(options);
}
private Task OnRedirectToIdentityProvider(
RedirectToIdentityProviderNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
{
if (notification.ProtocolMessage.RequestType == OpenIdConnectRequestType.LogoutRequest)
{
var currentPolicy =
notification.OwinContext.Authentication.AuthenticationResponseRevoke.AuthenticationTypes
.FirstOrDefault(x => x.StartsWith("b2c"));
notification.ProtocolMessage.IssuerAddress = notification.ProtocolMessage.IssuerAddress.Split('?')[0];
notification.ProtocolMessage.Parameters.Add("p", currentPolicy);
}
else
{
**// The value right now for the state is sort of "hijacked" and assigned by Microsoft**
//notification.ProtocolMessage.Parameters["state"] = "OpenIdConnect.AuthenticationProperties=sRt-teBcxsd239viWo...... ";
var currentPolicy = notification.OwinContext.Authentication.AuthenticationResponseChallenge.Properties
.Dictionary[PolicyKey];
notification.ProtocolMessage.IssuerAddress = notification.ProtocolMessage.IssuerAddress.Split('?')[0];
notification.ProtocolMessage.Parameters.Add("p", currentPolicy);
}
return Task.FromResult(0);
}
private async Task OnSecurityTokenValidated(SecurityTokenValidatedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
{
await MyClass.CreatePrincipal(notification.AuthenticationTicket.Identity);
}
private Task AuthenticationFailed(
AuthenticationFailedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
{
notification.HandleResponse();
notification.Response.Redirect("/Home/Error?message=" + notification.Exception.Message);
return Task.FromResult(0);
}
Similar to what Gaurav is suggesting, but adding a few special considerations. Basically, the state is used by the Owin middleware, so while you can inject your own stuff, you need to make sure you revert it back before the Owin middleware tries to use it otherwise you'll get auth errors.
This is effectively what I replied to a very similar question:
Custom parameter with Microsoft.Owin.Security.OpenIdConnect and AzureAD v 2.0 endpoint
In Startup.Auth.cs, when you setup the OpenIdConnectAuthenticationOptions you'd add the following:
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
//...
Notifications = new OpenIdConnectAuthenticationNotifications
{
RedirectToIdentityProvider = OnRedirectToIdentityProvider,
MessageReceived = OnMessageReceived
},
});
And use RedirectToIdentityProvider to inject your parameter, something along the lines of:
private static Task OnRedirectToIdentityProvider(RedirectToIdentityProviderNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
{
var stateQueryString = notification.ProtocolMessage.State.Split('=');
var protectedState = stateQueryString[1];
var state = notification.Options.StateDataFormat.Unprotect(protectedState);
state.Dictionary.Add("mycustomparameter", "myvalue");
notification.ProtocolMessage.State = stateQueryString[0] + "=" + notification.Options.StateDataFormat.Protect(state);
return Task.FromResult(0);
}
And then use MessageReceived to extract it, like so:
private static Task OnMessageReceived(MessageReceivedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
{
string mycustomparameter;
var protectedState = notification.ProtocolMessage.State.Split('=')[1];
var state = notification.Options.StateDataFormat.Unprotect(protectedState);
state.Dictionary.TryGetValue("mycustomparameter", out mycustomparameter);
return Task.FromResult(0);
}
You'd obviously need to improve/harden this but this should get you going.
Just add context.ProtocolMessage.SetParameter(<ParameterName>, <value>); in RedirectToIdentityProvider
Notifications = new OpenIdConnectAuthenticationNotifications()
{
RedirectToIdentityProvider = context =>
{
context.ProtocolMessage.SetParameter("prompt", "login");
return Task.FromResult(0);
},
}
};
You could pass the ProjectId parameter as value for State parameter. See the sample code below:
Notifications = new OpenIdConnectAuthenticationNotifications()
{
RedirectToIdentityProvider = context =>
{
redirectUri = string.Format("{0}/", System.Web.HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Authority));
postLogoutRedirectUri = redirectUri + "sign-out";
context.ProtocolMessage.RedirectUri = redirectUri;
context.ProtocolMessage.PostLogoutRedirectUri = postLogoutRedirectUri;
context.ProtocolMessage.State = "Your Project Id";
return Task.FromResult(0);
},
AuthorizationCodeReceived = context =>
{
var projectId = context.ProtocolMessage.State;//Retrieve the state in AuthorizationCodeReceived event.
return Task.FromResult(0);
}
}
};
UPDATE
Essentially State accepts a string parameter. In our project we needed to provide many values in the state. What we did there is created a pipe delimited string and pass that as state. When we receive the state back, we simply convert that into an array and use appropriate elements. Something like:
var state = "param1|param2|...|paramx";
Other thing you could do is create a state object (a simple class with some properties), serialize it as JSON, convert that in base64 string and pass that encoded string as state after properly url encoding it. When you receive back the state, you could do the reverse process, get state object back and use it's properties values.

Read and write cookies with #Push

In my vaadin application, i need to use #Push, but since i added it, i can't read and write cookies because VaadinService.getSurrentResponse()returns null because of Push. I manager cookies using this class :
import javax.servlet.http.Cookie;
import com.vaadin.server.VaadinResponse;
import com.vaadin.server.VaadinService;
public class CookieManager {
private VaadinResponse response;
public CookieManager(VaadinResponse response){
this.response = response;
}
public Cookie getCookieByName(final String name) {
// Fetch all cookies from the request
Cookie[] cookies = VaadinService.getCurrentRequest().getCookies();
// Iterate to find cookie by its name
for (Cookie cookie : cookies) {
if (name.equals(cookie.getName())) {
return cookie;
}
}
return null;
}
public Cookie createCookie(final String name, final String value, final int maxAge) {
// Create a new cookie
final Cookie cookie = new Cookie(name, value);
cookie.setMaxAge(maxAge);
// Set the cookie path.
cookie.setPath(VaadinService.getCurrentRequest().getContextPath());
// Save cookie
addCookie(cookie);
return cookie;
}
private void addCookie(Cookie cookie){
response.addCookie(cookie);
}
public Cookie updateCookieValue(final String name, final String value) {
// Create a new cookie
Cookie cookie = getCookieByName(name);
cookie.setValue(value);
// Save cookie
addCookie(cookie);
return cookie;
}
public void destroyCookieByName(final String name) {
Cookie cookie = getCookieByName(name);
if (cookie != null) {
cookie.setValue(null);
// By setting the cookie maxAge to 0 it will deleted immediately
cookie.setMaxAge(0);
cookie.setPath(VaadinService.getCurrentRequest().getContextPath());
addCookie(cookie);
}
}
}
When i want to create a cookie (like at user's login), i get a nullPointerException because of the VaadinResponse being null.
So i tried to disable Push in constructor and re-enable it at the end of addCookie()method, but it disabled push for all of my application, even if i re-enable it just after the addCookiemethod.
I saw a ticket on vaadin's trac (http://dev.vaadin.com/ticket/11808) saying that will not be fixed, and someone suggested to create a regular AJAX query from server to create cookie, but i really don't know how to do.
How can i manage my cookies? i need to create AND get cookies, so javascript can't help me there, because i can't get javascript's return in vaadin, so i can't get a cookie.
Here is my solution how to store cookie when #Push is using.
First we create container to storage all instance of client UI. (
This container itself has a great potential)
public class UISession {
private List<WebAppUI> uis = new ArrayList<WebAppUI>();
public void addUI(WebAppUI webAppUI) {
uis.add(webAppUI);
}
public List<WebAppUI> getUIs() {
return uis;
}
public static UISession getInstance() {
try {
UI.getCurrent().getSession().lock();
return (UISession) UI.getCurrent().getSession().getAttribute("userUiSession");
} finally {
UI.getCurrent().getSession().unlock();
}
}
In UI.init() we add new instance to the session (e.g when user open new tab)
#Override
protected void init(VaadinRequest vaadinRequest) {
/** Set singleton uisesison for each browser*/
if(UISession.getInstance()==null){
UI.getCurrent().getSession().setAttribute("userUiSession",new UISession());
}
UISession.getInstance().addUI(this);
System.out.println("UI count fo current browser "+UISession.getInstance().getUIs().size());
...
}
Here is my helper cookie class:
class MyCookie{
private String value;
private String name;
private Date expired;
private String path="/";
public MyCookie(String name, String value) {
this.name=name;
this.value=value;
}
public void setMaxAge(int minute) {
Calendar c = Calendar.getInstance();
c.add(Calendar.MINUTE, minute);
expired=c.getTime();
}
public String getStringToCreateCookie(){
return "document.cookie=\""+getName()+"="+getValue()+"; expires="+expired.toString()+"; path="+path+"\"";
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public Date getExpired() {
return expired;
}
public void setExpired(Date expired) {
this.expired = expired;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
}
And on final when we need add new cookie, we just must find Ui that is active and call js function
public static void addCookie(String name, String value, int age){
MyCookie myCookie = new MyCookie(name, value);
myCookie.setMaxAge(age);
for(WebAppUI ui : UISession.getInstance().getUIs()){
if(ui.isAttached()){
ui.getPage().getJavaScript().execute(myCookie.getStringToCreateCookie());
return;
}
}
}
In my case i have access to storage cookie (when user made request). I just only have problem with add new cookie so this is my working solutions.
As mentioned in the ticket, you can use JavaScript to call client code and also request a cookie value back by that. E.g.
#Grapes([
#Grab('org.vaadin.spring:spring-boot-vaadin:0.0.3'),
#Grab('com.vaadin:vaadin-server:7.4.0.beta1'),
#Grab('com.vaadin:vaadin-client-compiled:7.4.0.beta1'),
#Grab('com.vaadin:vaadin-themes:7.4.0.beta1'),
])
import com.vaadin.ui.*
#org.vaadin.spring.VaadinUI
#groovy.transform.CompileStatic
class MyUI extends UI {
protected void init(com.vaadin.server.VaadinRequest request) {
final resultLabel = new Label()
// provide a callback for the client to tell the cookies
JavaScript.current.addFunction("tellCookie", { elemental.json.JsonArray arguments ->
resultLabel.value = arguments?.get(0)?.asString()
} as JavaScriptFunction)
setContent(new VerticalLayout().with{
addComponent(new Button("Set Cookie", {
// just simply set the cookies via JS (attn: quoting etc)
JavaScript.current.execute("document.cookie='mycookie=${System.currentTimeMillis()}'")
} as Button.ClickListener))
addComponent(new Button("Get Cookie", {
// tell the client to tell the server the cookies
JavaScript.current.execute("this.tellCookie(document.cookie)")
} as Button.ClickListener))
addComponent(resultLabel)
return it
})
}
}
This is a running example (e.g. spring run vaadin.groovy) for testing. See the comments for the important parts.
The Viritin add-on contains a helper class called BrowserCookie. It works in pretty much the way suggested by cfrick, but just hides all the cookie handling complexity into a helper class. It don't contain built in "max age" handling yet, but that could be easily added as a workaround you can manually "encode" the age into cookie value.
BTW. Don't know what you are doing, but if you happen to be using TouchKit add-on, it has a helper for html5 local storage. It has rather wide browsers support already and is in many ways better way to store e.g. settings than cookies.

Resources