I have a .NET MVC website where the user is authenticated using AzureAD.
All the controllers are decorated with [Authorize] attribute.
Considering Security, we don't want unauthenticated users to access/download static content of the application like images, .js, .css files.
Can anyone suggest a good approach.
I figured this out and successfully implemented this by using the following:
Add app.UseStageMarker(PipelineStage.Authenticate); at the end
of app.UseOpenIdConnectAuthentication(). Please refer the below code of Startup class.
Add below in Web.configs.
Under <system.webServer> add the below
public partial class Startup
{
public Startup()
{
}
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
CookieManager = new SystemWebChunkingCookieManager(),
});
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
MetadataAddress = String.Format(Globals.WellKnownMetadata, Globals.TenantId, Globals.DefaultPolicy),
ClientId = Globals.ClientId,
RedirectUri = Globals.RedirectUri,
PostLogoutRedirectUri = Globals.LogoutPostUri,
TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
},
ResponseType = OpenIdConnectResponseType.CodeIdToken,
Scope = OpenIdConnectScope.OpenIdProfile + OpenIdConnectScope.OfflineAccess,
CookieManager = new SystemWebCookieManager()
}
);
app.UseStageMarker(PipelineStage.Authenticate);
}
}
The solution posted by Saca pointed me in the right direction, but adding the JS to every page was not a valid solution for me. There were thousands of HTML files, lots with no common JS file I could tack that ADAL code into. I would have had to find a way to insert that JS on all those pages.
My first solution was simply creating a normal .NET MVC app with the proper auth configured. Then I simply loaded this legacy content via an iFrame. This worked but was limiting for the users.
As Fei Xue mentioned in another comment, the next solution involved scrapping the iFrame but routing all requests for static files through a controller. Using this as a reference for understanding that: https://weblogs.asp.net/jongalloway/asp-net-mvc-routing-intercepting-file-requests-like-index-html-and-what-it-teaches-about-how-routing-works
The above solutions worked. However, eventually this app ended up as an Azure App Service and I simply turned on authentication at the app service level with just the pure html files
Related
I'm trying to create a signup/registration page for my Xamarin.Forms app. I've done a fair bit of reading on this and I've learned that authentication is a complicated process so it's best to use a service like OAuth or Azure Active Directory. I even created an account with Azure and connected my app (I think) but the documentation is sparse as far as implementing a registration page. OAuth seemed to have a pretty good tutorial on their site but it was only for Android and iOS, and my app is for Android & UWP. Is there anyone that can show how to implement a registration page in Xamarin.Forms? I don't have much of a user base yet so I'd like to use a service that's free or very cost effective, maybe like a pay per user model.
After connecting my Azure project in Visual Studio this code was auto-generated in a file called Startup.Auth.cs:
public partial class Startup
{
public void ConfigureAuth(IAppBuilder app)
{
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Tenant = ConfigurationManager.AppSettings["ida:Tenant"],
TokenValidationParameters = new TokenValidationParameters
{
ValidAudience = ConfigurationManager.AppSettings["ida:Audience"]
},
MetadataAddress = ConfigurationManager.AppSettings["ida:MetadataAddress"],
});
}
}
And this file Startup.MobileApp.cs:
public partial class Startup
{
public static void ConfigureMobileApp(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
new MobileAppConfiguration()
.UseDefaultConfiguration()
.ApplyTo(config);
// Use Entity Framework Code First to create database tables based on your DbContext
Database.SetInitializer(new MobileServiceInitializer());
MobileAppSettingsDictionary settings = config.GetMobileAppSettingsProvider().GetMobileAppSettings();
if (string.IsNullOrEmpty(settings.HostName))
{
app.UseAppServiceAuthentication(new AppServiceAuthenticationOptions
{
// This middleware is intended to be used locally for debugging. By default, HostName will
// only have a value when running in an App Service application.
SigningKey = ConfigurationManager.AppSettings["SigningKey"],
ValidAudiences = new[] { ConfigurationManager.AppSettings["ValidAudience"] },
ValidIssuers = new[] { ConfigurationManager.AppSettings["ValidIssuer"] },
TokenHandler = config.GetAppServiceTokenHandler()
});
}
app.UseWebApi(config);
}
}
But I have no idea how to work with these. What I do know is how to make a page with xaml and work with user inputs in the code behind (C#), but once I have user input like a new user name and such how do I connect to the authentication service? Any help would be appreciated, either in the form of detailed instructions or a link to a tutorial. Also I am not looking to use a 3rd party login such as login with Google or login with Facebook.
I have a web based app. This app allows users to sign up/in using Google Auth as per this code in Startup.cs
services.AddAuthentication().AddGoogle(googleOptions =>
{
googleOptions.ClientId = Configuration["ClientId"];
googleOptions.ClientSecret = Configuration["CliSecret"];
...
});
This all works nicely with the out-of-the-box Identity system so I can register users.
However, I also want users to be able to 'connect' to other Google services with separate accounts after the sign up in a separate area of the site.
For example, I might want a user to connect their AdWords account.
They will authenticate with Google via a non-Identity flow and the relevant info (token, refresh token etc) will be stored independantly in the db (i.e it won't store a 'User' in the AspNetUSers table).
Can I change the authentication scope in the controller before I make my initial call to google?
It'd be nice to utilize the same Authentication service but with some extra scope in this case. Is that possible?
Alternatively, have another Google section in Startup.cs...maybe like:
services.AddAuthentication().AddGoogle(googleOptions =>
{
googleOptions.ClientId = Configuration["ClientId"];
googleOptions.ClientSecret = Configuration["CliSecret"];
googleOptions.Scope.Add("https://www.googleapis.com/auth/adwords"); //*** THIS IS THE EXTRA SCOPE NEEDED ***
...
});
We had similar problem, our Identity Provider should be able to login users of defferent clients with different Google account
We decided to add multiple Google areas as you suggested. The main point here is that each area (which defines some google account) uses unique cookie scheme.
When we create login URL, we get google account needed for that client, get it's cookie scheme and create correct URL for Google Authenticate button
code example:
public static class AuthenticationBuilderGoogleAdder
{
public static AuthenticationBuilder AddGoogleAuth(this AuthenticationBuilder authenticationBuilder, IServiceCollection services)
{
var serviceProvider = services.BuildServiceProvider();
// create IThirdPartyProvidersProvider realization with GetByProviderCode method
var authThirdPartyProvidersProvider = serviceProvider.GetService<IThirdPartyProvidersProvider>();
var googleProviders = authThirdPartyProvidersProvider.GetByProviderCode("google");
googleProviders.ForEach(p =>
{
authenticationBuilder = authenticationBuilder.AddGoogle(p.CookieScheme, options =>
{
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
options.ClientId = p.ClientId;
options.ClientSecret = p.ClientSecret;
});
});
return authenticationBuilder;
}
}
register it as
services.AddAuthentication()
.AddGoogleAuth(services)
We call services.BuildServiceProvider() in order to create another container with services which were already registered in DI, in order to get Google accounts with different cookie schemas from the DB
Have a weird thing happening here.
I have built an ASP.NET MVC5 website, and have local accounts working fine via ASP.NET Identity.
I am now trying to enable external authentication, but have some weirdness happening.
I'm certain I've followed the right steps. I have this in my Startup.Auth.cs:
public void ConfigureAuth(IAppBuilder app)
{
// Enable the application to use a cookie to store information for the signed in user
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login")
});
// Use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
// Uncomment the following lines to enable logging in with third party login providers
//app.UseMicrosoftAccountAuthentication(
// clientId: "",
// clientSecret: "");
//app.UseTwitterAuthentication(
// consumerKey: "",
// consumerSecret: "");
//app.UseFacebookAuthentication(
// appId: "",
// appSecret: "");
app.UseGoogleAuthentication();
}
When the user clicks on the link to logon with Google, the ExternalLogin method is called:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult ExternalLogin(string provider, string returnUrl)
{
// Request a redirect to the external login provider
return new ChallengeResult(provider, Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl }));
}
Which I have verified via debugging gets into the ExecuteResult method of the ChallengeResult class:
public override void ExecuteResult(ControllerContext context)
{
var properties = new AuthenticationProperties() { RedirectUri = RedirectUri };
if (UserId != null)
{
properties.Dictionary[XsrfKey] = UserId;
}
context.HttpContext.GetOwinContext().Authentication.Challenge(properties, LoginProvider);
}
However, in the browser, nothing happens. I just get a blank page where I would expect a redirect to the Google signin page.
There are no errors reported at all.
The other funny thing is that I tried to create another MVC5 application, but I get a "Object reference not set to an instance of an object" popup in VS2013, and the resultant project is missing the Account controller that is usually there by default.
I've repaired the installation of VS2013, I have re-installed Update 1, and I've also updated all Nuget packages in the solution.
I've run out of ideas of where to go next.
Update 1
Thinking that this may be related to my PC, I've deployed the website to Azure, and the problem still persists. Does this mean it could be related to a missing assembly, and it's not being reported correctly?
I've run fusionlog, but I see no binding failures.
Spinning up a new VM with a clean install of Windows 8 and VS2013 to see if I can get a new project working there.
Update 2
Ok, just ran another round of "network" capturing, and when the user selects the external provider, it DOES post to the ExternalLogin action, but the response is a 401 Unauthorized. What could be causing this?
Ok,
I figured out what (the important part) of the issue was.
Firstly, I still don't known why when I create an MVC project I don't get a scaffold'ed AccountController class.
However, it seems my issue is that my login button was passing "google" instead of "Google" as the provider.
Seriously!? I am a little surprised that casing would matter with the name of the provider, but there you go.
This is not the answer to your question but it respond a very similar problem
If you had Owin 2.0 and you migrate to 2.1
The _ExternalLoginsListPartial need to change from this:
<button type="submit" class="btn btn-default" id="#p.AuthenticationType" name="Partner" value="#p.AuthenticationType" title="Log in using your #p.Caption account">#p.AuthenticationType</button>
to this
<button type="submit" class="btn btn-default" id="#p.AuthenticationType" name="provider" value="#p.AuthenticationType" title="Log in using your #p.Caption account">#p.AuthenticationType</button>
The only thing that change is the name of the button but that could break your head like mine and cost me 2 days of work to find the problem.
Maybe this is explained in the new Owin version, if not must be something to specify in capitals.
I've just ran into the same issue with my ASP.Net WebAPI server. I was passing the correct AuthenticationType into the challenge method but still getting a blank page.
Turns out the issue was the order I added the OAuth providers into the OWIN pipeline at startup.
Roughly my working order:
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
OAuthBearerOptions = new OAuthBearerAuthenticationOptions();
var authServerOptions = new OAuthAuthorizationServerOptions
{
...
};
// Token Generation
app.UseOAuthAuthorizationServer(authServerOptions);
app.UseOAuthBearerAuthentication(OAuthBearerOptions);
// Configure Google External Login
GoogleAuthOptions = new GoogleOAuth2AuthenticationOptions()
{
ClientId = xxxx,
ClientSecret = xxxx,
Provider = new GoogleAuthProvider()
};
app.UseGoogleAuthentication(GoogleAuthOptions);
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
app.UseWebApi(config);
Previously I was adding the UseCors + UseWebApi methods before UseGoogleAuthentication method.
I found this post to be useful too:
http://coding.abel.nu/2014/06/understanding-the-owin-external-authentication-pipeline/
I am in the process of changing our authentication implementation to use MVC5 ASP.NET Identity with Owin.
However we need to integrate our sign in with other linked applications and websites on the same domain. We currently do this by sharing the cookie between applications across a number of subdomains. The cookie in a very specific format and encryption algorithm that a variety of applications and technologies (ie not all are in .NET or on the same server) can use.
I have found that in the App_Start ConfigureAuth.cs you can set the app.UseCookieAuthentication to specify things like the cookie name and the subdomain of the cookie (eg ASP.NET Identity Cookie across subdomains).
This is a very good start, but I also need to change the actual value of the cookie to be a specific format and encryption algorithm.
Does anyone know how to customize the value and encryption type used to create and read the cookie?
Thanks for any help,
Saan
CookieAuthenticationOptions class has a property called TicketDataFormat which is meant for this purpose. You can implement a custom ISecureDataFormat object and achieve this. A default has been assigned for this TicketDataFormat if you have not overridden this.
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
TicketDataFormat = new MyCustomSecureDataFormat()
});
public class MyCustomSecureDataFormat : ISecureDataFormat<AuthenticationTicket>
{
private static AuthenticationTicket savedTicket;
public string Protect(AuthenticationTicket ticket)
{
//Ticket value serialized here will be the cookie sent. Encryption stage.
//Make any changes if you wish to the ticket
ticket.Identity.AddClaim(new Claim("Myprotectionmethod", "true"));
return MySerializeAndEncryptedStringMethod(ticket);
}
public AuthenticationTicket Unprotect(string cookieValue)
{
//Invoked everytime when a cookie string is being converted to a AuthenticationTicket.
return MyDecryptAndDeserializeStringMethod(cookieValue);
}
}
I am completely new to OWIN authentication, and I must be misunderstanding how everything works, but I can't find this mentioned anywhere.
All I want is to be able to use a central domain for authentication. If someone tries to access apps.domain.com when not authenticated, they will be redirected to accounts.domain.com/login so that all the authentication is separated into it's own domain and application. This was very easy with MVC 4 forms authentication where you can specify a full URL, but doesn't seem to be with OWIN.
In Startup.Auth.cs:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
LoginPath = new PathString("/account/login")
}
It's easy to specify the domain when setting the cookie with the CookieDomain option. However, when you specify the login path to redirect to, it has to be relative to the current application, so how do I go about accomplishing what was so easy in MVC 4 forms authentication?
Without getting too deep into what OWIN authentication is all about, I could not find anything addressing this after a couple hours of searching.
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
LoginPath = new PathString("/account/login"),
LogoutPath = new PathString("/account/logout"),
Provider = new CookieAuthenticationProvider
{
OnApplyRedirect = ApplyRedirect
},
});
}
private static void ApplyRedirect(CookieApplyRedirectContext context)
{
Uri absoluteUri;
if (Uri.TryCreate(context.RedirectUri, UriKind.Absolute, out absoluteUri))
{
var path = PathString.FromUriComponent(absoluteUri);
if (path == context.OwinContext.Request.PathBase + context.Options.LoginPath)
{
context.RedirectUri = "http://accounts.domain.com/login" +
new QueryString(
context.Options.ReturnUrlParameter,
context.Request.Uri.AbsoluteUri);
}
}
context.Response.Redirect(context.RedirectUri);
}
}
If apps.domain.com is the only return URL base possible, you should strongly consider replacing context.Request.Uri.AbsoluteUri with context.Request.PathBase + context.Request.Path + context.Request.QueryString and build an absolute return URL in your authentication server to protect your apps from abusive redirects.
Hope this helps ;)
EDIT: you might ask yourself why I don't directly apply the redirect using the context.RedirectUri property. In fact, ICookieAuthenticationProvider.ApplyRedirect is responsible of multiple redirects, corresponding to the log-in and log-out flows (yep, I know, it breaks the single responsibility principle...). But there's even worse: context.RedirectUri can either represent the authentication endpoint's absolute URL in the beginning of the log-in flow or the final browser's destination (ie. the real relative "return URL") when the cookie is effectively being sent back to the browser... that's why we need to make sure that context.RedirectUri is absolute and corresponds to the registered context.Options.LoginPath.
I am working through the examples for https://github.com/IdentityServer/IdentityServer3 and I have a different answer. In the example at https://www.scottbrady91.com/Identity-Server/Identity-Server-3-Standalone-Implementation-Part-2 they show an MVC app that uses a standalone IdP and cookies authentication. The example hasn't included getting 401 redirects working, but I stumbled on a way.
The basic scheme is to create an action in the AccountController for logging on.
public ActionResult SignIn() {
// set up some bookkeeping and construct the URL to the central auth service
return Redirect(authURL);
}
Now you have a local URL that can be used in the Startup
public class Startup {
public void Configuration(IAppBuilder app) {
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Cookies",
LoginPath = new PathString("/Account/SignIn")
});
}
You also have the added benefit that you can put an action link to the SignIn on the menu bar, for people who want to log on before there is a 401. What we've done here is decoupled the decision of what to do when an unathenticated user asks for a resource from how the authentication is obtained.