Reputation: 47
I recently posted a question which has been answered but led to this new problem. If interested, it can be seen at Previous post.
Intro
I am currently developing an application using AD-B2C as my identity provider. This is integrated into the solution using their guidelines at AD B2C graph, which uses openid-connect.
I need to use a form of email activation (outside of their register policy) and as such I need to be able to pass a value from the URL in the email, through the sign-up process at B2C and back to the redirection URL.
For this we use the state parameter.
Problem
In my OnRedirectToIdentityProvider I encrypt the state
private Task OnRedirectToIdentityProvider(RedirectToIdentityProviderNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
{
var temp = notification.ProtocolMessage.State;
// To be used later
var mycustomparameter = notification.OwinContext.Get<string>("mycustomparameter");
if (notification.ProtocolMessage.State != null)
{
var stateQueryString = notification.ProtocolMessage.State.Split('=');
var protectedState = stateQueryString[1];
var state = notification.Options.StateDataFormat.Unprotect(protectedState);
state.Dictionary.Add("mycustomparameter", "testing");
notification.ProtocolMessage.State = stateQueryString[0] + "=" + notification.Options.StateDataFormat.Protect(state);
}
return Task.FromResult(0);
}
This works for all I can tell.
Now the user is passed to the sign in on the AD B2C and is after the login redirected back where the OnMessageReceived is triggered.
private 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);
}
this is where it breaks. In the ...StateDataFormat.Unprotect(protectedState)
It throws an error System.Security.Cryptography.CryptographicException with the message "Error occurred during a cryptographic operation."
EDIT: Stacktrace:
System.Web.dll!System.Web.Security.Cryptography.HomogenizingCryptoServiceWrapper.HomogenizeErrors(System.Func<byte[], byte[]> func, byte[] input) Unknown
System.Web.dll!System.Web.Security.Cryptography.HomogenizingCryptoServiceWrapper.Unprotect(byte[] protectedData) Unknown
System.Web.dll!System.Web.Security.MachineKey.Unprotect(System.Web.Security.Cryptography.ICryptoServiceProvider cryptoServiceProvider, byte[] protectedData, string[] purposes) Unknown
System.Web.dll!System.Web.Security.MachineKey.Unprotect(byte[] protectedData, string[] purposes) Unknown
Microsoft.Owin.Host.SystemWeb.dll!Microsoft.Owin.Host.SystemWeb.DataProtection.MachineKeyDataProtector.Unprotect(byte[] protectedData) Unknown
Microsoft.Owin.Security.dll!Microsoft.Owin.Security.DataProtection.AppBuilderExtensions.CallDataProtectionProvider.CallDataProtection.Unprotect(byte[] protectedData) Unknown
Microsoft.Owin.Security.dll!Microsoft.Owin.Security.DataHandler.SecureDataFormat<Microsoft.Owin.Security.AuthenticationProperties>.Unprotect(string protectedText) Unknown
IntellifyPortal.dll!IntellifyPortal.Startup.OnMessageReceived(Microsoft.Owin.Security.Notifications.MessageReceivedNotification notification) Line 171 C#
My attempts
I have tried specifying machine keys in the Web.config
I have tried messing with the "CallbackPath property in OpenIdConnectAuthenticationOptions, with no success.
I have tried a lot of diffent tweaks, but I can't seem to figure out why I can't "unprotect" the inbound state.
Any help is appreciated,
Best regards.
Update: Solution
I have decided to use an alternative method, which I found to work(hopefully it may of use to others): Azure-sample which I used as guidance
private Task OnRedirectToIdentityProvider(RedirectToIdentityProviderNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
{
var policy = notification.OwinContext.Get<string>("Policy");
if (!string.IsNullOrEmpty(policy) && !policy.Equals(DefaultPolicy))
{
notification.ProtocolMessage.Scope = OpenIdConnectScopes.OpenId;
notification.ProtocolMessage.ResponseType = OpenIdConnectResponseTypes.IdToken;
notification.ProtocolMessage.IssuerAddress = notification.ProtocolMessage.IssuerAddress.ToLower().Replace(DefaultPolicy.ToLower(), policy.ToLower());
}
// Accept Invitation Email
string testValue= notification.OwinContext.Get<string>("testValue");
string testValue2= notification.OwinContext.Get<string>("testValue2");
if (!string.IsNullOrEmpty(testValue) && !string.IsNullOrEmpty(testValue2))
{
var stateQueryString = notification.ProtocolMessage.State.Split('=');
var protectedState = stateQueryString[1];
var state = notification.Options.StateDataFormat.Unprotect(protectedState);
state.Dictionary.Add("testValue", testValue);
state.Dictionary.Add("testValue2", testValue2);
notification.ProtocolMessage.State = stateQueryString[0] + "=" + notification.Options.StateDataFormat.Protect(state);
}
return Task.FromResult(0);
}
private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification notification)
{
// Extract the code from the response notification
var code = notification.Code;
string signedInUserID = notification.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value;
TokenCache userTokenCache = new MSALSessionCache(signedInUserID, notification.OwinContext.Environment["System.Web.HttpContextBase"] as HttpContextBase).GetMsalCacheInstance();
ConfidentialClientApplication cca = new ConfidentialClientApplication(ClientId, Authority, RedirectUri, new ClientCredential(ClientSecret), userTokenCache, null);
try
{
AuthenticationResult result = await cca.AcquireTokenByAuthorizationCodeAsync(code, Scopes);
// Look for acceptInvitation
string testValue;
string testValue2;
var protectedState = notification.ProtocolMessage.State.Split('=')[1];
var state = notification.Options.StateDataFormat.Unprotect(protectedState);
state.Dictionary.TryGetValue("testValue", out testValue);
state.Dictionary.TryGetValue("testValue2", out testValue2);
// InvitationAccept / store values
if(!string.IsNullOrEmpty(testValue) && !string.IsNullOrEmpty(testValue2))
{
// How can I pass values to the redirect controller?
// Can I somehow transfer it from here to that destination
}
}
catch (Exception ex)
{
//TODO: Handle
throw;
}
}
Final Question
I can now receive the values back as expected. These values has to be used in creating a relation between the new account and other accounts/groups in the application.
I therefore want to transfer these values back to the application (controller) for processing. I've tried storing the values in the context, in the response headers and in the claims to no avail. I guess this is because that this is the "middleware" and that the actual "redirect" happens directly from AD B2C thus not holding my params.
Can I somehow get the params back to the controller as well, without relying on the request URI (originating from the original user link) - Preferably directly in the claims, so that a user already logged in does not have to "re-signin" upon clicking the link.
How can I get my values (in the state, which are handled in the OnMessageRecieved) passed to the controller which is redirected to?
Upvotes: 2
Views: 1423
Reputation: 1
You're not supposed to decrypt the hint. Instead of this:
ProtocolMessage.State.Split('
Remove the hint so you only have encrypted data:
ProtocolMessage.State.Parameters["state"].Replace("OpenId.AuthenticationOptions=","")
Then you can you decrypt value of sate:
StateDataFormat.Unprotect("TC%$t43tj9358utj3")
It should deserialize to AuthenticationOptions.
Upvotes: 0