Web Forms Authentication with B2C - c#

I'm trying to add authentication using Azure AD B2C to a web forms app. Unfortunately, every tutorial I've found is for MVC, except for this web forms tutorial. Using that tutorial, I've added this code to my startup.auth.cs:
public partial class Startup {
// For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301883
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = "my-client-id",
Authority = "https://login.microsoftonline.com/my-tenant"
});
}
}
And that is working fine. However, I need to have sign up functionality as well as just sign-in, but I can't figure out how to do it, since everything I've found is for MVC, and I'm not sure how to convert that to what I need. I've tried adding code such as this:
app.UseOpenIdConnectAuthentication(CreateOptionsFromPolicy(_SignUpPolicyId));
app.UseOpenIdConnectAuthentication(CreateOptionsFromPolicy(_ProfilePolicyId));
app.UseOpenIdConnectAuthentication(CreateOptionsFromPolicy(_SignInPolicyId));
And that creates three more buttons on the login page, but clicking on them just gives a 404 error and no extra information, so I don't know how to make that work, either, or even if I'm headed in the right direction. I've never worked with B2C before, so if anyone has any suggestions/has done this sort of thing for web forms, I'd really appreciate some tips or sample code.

The example you are using is using "Local Accounts"
Local Accounts mean a local database, and for each Idenity provider it will add a button.
Try to change the authentication to "No Authentication" (and add all the files yourself) or "Work and School Accounts" (which connects to an AD, so convert that to B2C).
You will see a redirect to the https://login.microsoftonline.com/yourtenant.onmicrosoft.com/....
The next steps are to follow the same steps as with the MVC example, implement the same pieces of code.
Make sure to update the nuget packages to a newer version(1.0 and 4.0 are the default):
<package
id="Microsoft.IdentityModel.Protocol.Extensions"
version="1.0.2.206221351"
targetFramework="net46" />
<package
id="System.IdentityModel.Tokens.Jwt"
version="4.0.2.206221351"
targetFramework="net46" />
And the code:
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(CreateOptionsFromPolicy(signInPolicyId));
}
private OpenIdConnectAuthenticationOptions CreateOptionsFromPolicy(string policy)
{
return new OpenIdConnectAuthenticationOptions
{
MetadataAddress = string.Format(aadInstance, tenant, policy),
AuthenticationType = policy,
ClientId = clientId,
RedirectUri = "https://localhost:44300/",
PostLogoutRedirectUri = redirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications
{
},
Scope = "openid",
ResponseType = "id_token",
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
},
};
}
Add a /Account/SignIn.aspx page, and in the code behind place the code from the MVC sample SignIn:
if (!Request.IsAuthenticated)
{
// To execute a policy, you simply need to trigger an OWIN challenge.
// You can indicate which policy to use by adding it to the AuthenticationProperties using the
// PolicyKey provided.
HttpContext.Current.GetOwinContext().Authentication.Challenge(
new AuthenticationProperties()
{
RedirectUri = "/",
},
appConfiguration.B2CSignInPolicyId);
}

Related

How do I get id_token for keycloak using OWIN?

I'm working with an very old VB.net application trying to layer in SSO auth using OWIN and KeyCloak. This is all new to me. The approach I'm taking is to create a C# app to sit in between KeyCloak and my VB app. I've been able to get my C# app to open the login screen of KeyCloak, authenticate and return to the C# app or even the VB app. This seems fine.
However, I need the id_token and username to pass to the VB app. When using Fiddler I can see KeyCloak is generating a post back to my return page with the id_token in tow. However, it is on another thread and gets redirected to the original page but without the id_token. I must be missing something. I've seen code where there are notifications wired and I think they should grab the token and user info, but I don't know how to get the notifications to work. There is no explicit documentation to tell me what to do.
Am I supposed to have a listener or callback method to catch the post from KeyCloak? If so can some one show me how to create one?
Note: I've found some Microsoft code using OWIN and Azure and MVC that bring back user info. However, I point this same code to KeyCloak it authenticates but no user info is returned.
Any help will be greatly appreciated.
-Thanks
In my Startup.cs file I have the following (I've tried many different variations to no avail):
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(
CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
// Sets the ClientId, authority, RedirectUri as obtained from web.config
ClientId = _clientId,
ClientSecret = _clientSecret,
RequireHttpsMetadata = false,
Authority = _authority,
RedirectUri = _redirectUri,
// PostLogoutRedirectUri is the page that users will be redirected to after sign-out. In this case, it is using the home page
PostLogoutRedirectUri = _redirectUri,
Scope = OpenIdConnectScope.OpenIdProfile,
// ResponseType is set to request the id_token - which contains basic information about the signed-in user
ResponseType = OpenIdConnectResponseType.IdToken,
// ValidateIssuer set to false to allow personal and work accounts from any organization to sign in to your application
// To only allow users from a single organizations, set ValidateIssuer to true and 'tenant' setting in web.config to the tenant name
// To allow users from only a list of specific organizations, set ValidateIssuer to true and use ValidIssuers parameter
TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = true
},
// OpenIdConnectAuthenticationNotifications configures OWIN to send notification of failed authentications to OnAuthenticationFailed method
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = OnAuthenticationFailed,
SecurityTokenReceived = OnSecurityTokenReceived
}
}
);
}

Handle 404 exception in ASP.Net web API

I have ASP.Net web API in .net framework 4.8.
I have implemented single sign on using Azure active directory bearer token.
User will be redirected to login page if route is found in routing table. However, if user enters incorrect route (https://mysite/invalidurl) then it directly shows a yellow death screen with 404.
Is it possible to redirect user to login page first incase of invalid url (404) also? If yes then how? It means user enters any url (valid or invalid), s/he should be redirected to login page first.
I have already tried few options but none of the option worked for me like ExceptionHandler, AttributeRouting, CustomError in Web.Config, Application_Error in global.asax, global pattern matching in route table ( like this: routeTemplate: "api/{*uri}",)
Here is the code of my startup file
public void ConfigureAuth(IAppBuilder app)
{
List<string> audiences = ConfigurationManager.AppSettings["ida:Audience"].Split(AudienceDelimitter).ToList();
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
AuthenticationType = "OAuth2Bearer",
Tenant = ConfigurationManager.AppSettings["ida:TenantId"],
TokenValidationParameters = new TokenValidationParameters
{
ValidAudiences = audiences
}
});
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = authority,
PostLogoutRedirectUri = postLogoutRedirectUri,
RedirectUri = postLogoutRedirectUri
});
}
Thanks in advance.

How to add multiple endpoints to adfs

I have a lot of web applications on the same web server (II7):
let's say mydomain/app1, mydomain/app2, ... and so on.
I'm trying to add an ADFS authentication through OWIN.
Here's what I've done:
[assembly: OwinStartup(typeof(MyNamespace.Startup))]
namespace MyNamespace
{
public class Startup
{
private static string realm = ConfigurationManager.AppSettings["ida:Wtrealm"];
private static string adfsMetadata = ConfigurationManager.AppSettings["ida:ADFSMetadata"];
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
app.Use((context, next) =>
{
SignIn(context);
return next.Invoke();
});
app.UseStageMarker(PipelineStage.Authenticate);
}
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseWsFederationAuthentication(
new WsFederationAuthenticationOptions
{
Wtrealm = realm,
MetadataAddress = adfsMetadata
});
}
public void SignIn(IOwinContext context)
{
if (context.Authentication.User == null)
{
context.Authentication.Challenge(
WsFederationAuthenticationDefaults.AuthenticationType);
}
}
}
}
When a user access mydomain/app1, I want him to be authenticated through ADFS and then redirected to mydomain/app1. And same thing for a user accessing mydomain/app2.
But I wish to add only one relying party trust in ADFS (because there's a lot of applications and all are using same claim rules).
I've tried different configurations, but I can't do what I want:
if the RP endpoint is mydomain/app1/, authentication is ok but all requests (even from mydomain/app2 are redirected to app1), obviously
if the RP endpoint is only mydomain/, I get a 405.0 http error - Method Not Allowed after redirection (I take care of the trailing slash).
For information, I saw this question on stackoverflow:
URL redirection from ADFS server
But it doesn't really answer my problem because I don't understand sentence "(...) WIF will process the response at URL_1, and then take care of redirecting the user to URL_2" in Andrew Lavers's comment.
How can I add multiple endpoints to one RP trust ?
Or how can I redirect users to the original URL ? (considering all applications are on the same domain).
Thanks in advance for any help.
You should be able to set the wreply parameter based on the application that triggers the authentication flow. Something like this:
app.UseWsFederationAuthentication(
new WsFederationAuthenticationOptions
{
Wtrealm = realm,
MetadataAddress = adfsMetadata,
Notifications = new WsFederationAuthenticationNotifications
{
RedirectToIdentityProvider = context =>
{
context.ProtocolMessage.Wreply = <construct reply URL from context.Request>;
return Task.FromResult(0);
}
}
});
The issue here is that even by doing this the ADFS server does not need to comply with the given Wreply parameter. By default behaviour ADFS always re-directs to the Wtrealm after successful login.
In our case we wanted to authenticate via ADFS with 2 test servers, 1 production server and enable the login also for developers (localhost). Because of the re-direction issue each of the servers need their own Relying party trust .
The ideal solution here would be that RP trust is created separately for each server running the application and also for https://localhost:44300 (Visual Studio default SSL port) so that developers can also authenticate. For allowing https://localhost:44300 there is probably some security conserns to the preferred option would be to set up development ADFS for example on Azure VM.

OWIN OpenID Connect Middleware Not Replacing Current User with ClaimsPrincipal

I have an existing MVC5 application I am converting from using AspNetIdentity to utilize ThinkTecture Identity Server 3 v2. The OpenID provider is not the biggest issue I'm having, as it seems to be working great. The security token is validated and I'm handling the SecurityTokenValidated notification in a method in order to get additional user info claims and add system-specific permission claims to the claim set, similar to the code below:
OWIN Middleware
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
Authority = "https://localhost:44300/identity",
Caption = "My Application",
ClientId = "implicitclient",
ClientSecret = Convert.ToBase64String(SHA256.Create().ComputeHash(Encoding.UTF8.GetBytes("secret"))),
RedirectUri = "http://localhost:8080/",
ResponseType = "id_token token",
Scope = "openid profile email roles",
SignInAsAuthenticationType = CookieAuthenticationDefaults.AuthenticationType,
UseTokenLifetime = false,
Notifications = new OpenIdConnectAuthenticationNotifications
{
SecurityTokenValidated = ClaimsTransformer.GenerateUserIdentityAsync
}
});
Claims Transformer
public static async Task GenerateUserIdentityAsync(SecurityTokenValidatedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
{
var identityUser = new ClaimsIdentity(
notification.AuthenticationTicket.Identity.Claims,
notification.AuthenticationTicket.Identity.AuthenticationType,
ClaimTypes.Name,
ClaimTypes.Role);
var userInfoClient = new UserInfoClient(new Uri(notification.Options.Authority + "/connect/userinfo"),
notification.ProtocolMessage.AccessToken);
var userInfo = await userInfoClient.GetAsync();
identityUser.AddClaims(userInfo.Claims.Select(t => new Claim(t.Item1, t.Item2)));
var userName = identityUser.FindFirst("preferred_username").Value;
var user = MembershipProxy.GetUser(userName);
var userId = user.PersonID;
identityUser.AddClaim(new Claim(ClaimTypes.Name, userId.ToString(), ClaimValueTypes.Integer));
// Populate additional claims
notification.AuthenticationTicket = new AuthenticationTicket(identityUser, notification.AuthenticationTicket.Properties);
}
The problem is that the ClaimsIdentity assigned to the authentication ticket at the end of the transformer is never populated in the System.Web pipeline. When the request arrives to my controller during OnAuthenticationChallenge, I inspect the User property to find an anonymous WindowsPrincipal with a similar WindowsIdentity instance assigned to the Identity property, as if the web config's system.web/authentication/#mode attribute were set to None (at least I believe that's the behavior for that mode).
What might cause a failure by the middleware to set the principal for the user, or for it to be replaced during System.Web's processing with an anonymous Windows identity? I haven't been able to track this down.
EDIT: This occurs irrespective of whether the SecurityTokenValidated notification is handled and claims augmented.
EDIT 2: The cause appears to be making use of ASP.NET State Service session state server in my web.config with cookies. The configuration entry is:
<sessionState mode="StateServer" stateConnectionString="tcpip=127.0.0.1:42424" timeout="30" cookieless="UseCookies" />
This looks to be related to a reported issue in Microsoft.Owin.Host.SystemWeb #197 where cookies are not persisted from the OWIN request context into the System.Web pipeline. There are an assortment of workarounds suggested but I'd like someone to authoritatively point me to a properly vetted solution for this problem.
If you were to capture a fiddler trace, do you see the any .AspNet.Cookies.
I suspect the cookies are not being written. You can use a distributed cache and do away with cookies.
I think you need to include cookie authentication middleware because .AspNet.Cookies thing is written by that middleware. This is how you can integrate that middleware
app.UseCookieAuthentication(new CookieAuthenticationOptions());
Note: Please make sure it should be on top of openid connect middleware
for more details about CookieAuthenticationOptions please goto this link https://msdn.microsoft.com/en-us/library/microsoft.owin.security.cookies.cookieauthenticationoptions(v=vs.113).aspx.

WebForms authentication against Azure AD

I have a WebForms site that has been running on an internal server and authenticating users against our internal Active Directory. Due to some new features that we are implementing, this site needs to be moved to an external server and then authentication changed so that it authenticates users against our Office 365 accounts. To this end I have:
Created a new WebForms site (not using MVC)
Set up a new application in Azure.
Modified the Startup.Auth.cs as follows:
public void ConfigureAuth(IAppBuilder app)
{
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
}
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions { ClientId = "MyApplicationGUID", Authority = "https://login.windows.net/MyDomain.com" });
When I go to the default page and click Log On, it takes me to the correct Login page and the button for OpenID is shown. If I click the button, I am taken to the Microsoft Login page where I am able to enter my credentials. However, at that point, I am redirected back to my site's login page where it is still asking for a username/password.
What I would like to have happen is to set the site up so that if a user is not authenticated, they are redirected directly to the Microsoft login page and upon successful login are redirected back to the page they requested originally. Failing this, I would be satisfied with getting the default login page working so that when I click OpenID I'm not redirected back to the login page.
I don't have time to learn MVC at this point and port the whole thing over so going that route is not an option at this time.
I don't know enough about this process, so if my question doesn't make sense or if you need more information, please let me know and I'll be glad to try and find what you need to assist me in this.
Maybe I'm missing something, but I don't see why you need the custom login page or the external signin cookie. A typical Startup.Auth for OIDC/AAD looks something like this:
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = "AppGUID",
Authority = "https://login.windows.net/MyDomain.com",
// After authentication return user to the page they were trying
// to access before being redirected to the Azure AD signin page.
Notifications = new OpenIdConnectAuthenticationNotifications()
{
RedirectToIdentityProvider = (context) =>
{
string currentUrl = context.Request.Scheme + "://" + context.Request.Host + context.Request.Path;
context.ProtocolMessage.RedirectUri = currentUrl;
return Task.FromResult(0);
}
}
});
The cookie auth is just to keep from going to AAD for every single request. All the real work happens in the OpenIdConnectAuthentication.
Here's an example of WebForms, Azure AD, and OpenID Connect:
http://www.cloudidentity.com/blog/2014/07/24/protecting-an-asp-net-webforms-app-with-openid-connect-and-azure-ad/

Categories