So, I'm not super strong on the intricancies of the new OWIN framework and have the following issue:
I have a SPA web application (latest MVC, AngularJs), latest OWIN, Identity framework is connected to RavenDB user store (using Brock's provider)..
Need: Login user thru username/password or thru Twitter/Fb/LiveId on the SPA website and have that action authenticate future Web API calls made from the browser in a secure fashion. During authentication, I need to pass a number of claims to Web API so that it knows not only who the user is, but also his/her permissions.
Furthermore, need to have Web API be called as an API with use of API Keys (this part I think I got).
What's the proper way to do this? I'm also planning to have mobile apps connect to the Web API in the future too. Been reading numerous articles and my head is spinning already.
Issue: I thought using cookie authentication mode would do it for me. I've got the setup working for Visual Studio debugging mode and thought I was good. However, when I deployed into Azure, I've started getting 401 Unauthorized from API calls. Both the SPA website and API are running under the same root domain but different subdomains. I'm not using any cookie domain setting when testing locally from Visual Studio. I'm using root domain for cookies when in Azure.
Here's my SPA's MVC application ConfigAuth:
public void ConfigureAuth(IAppBuilder app)
{
var decryptor = new SettingsEncryption();
// 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"),
LogoutPath = new PathString("/Account/Logout"),
CookieDomain = CloudConfigurationManager.GetSetting("AuthCookieDomain"),
CookieSecure = CookieSecureOption.Always,
//CookiePath = "/",
CookieHttpOnly = true,
ExpireTimeSpan = TimeSpan.FromDays(365),
SlidingExpiration = true,
});
// Use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
app.UseMicrosoftAccountAuthentication(
clientId: decryptor.Decrypt(CloudConfigurationManager.GetSetting("LiveIdOAuthAppId")),
clientSecret: decryptor.Decrypt(CloudConfigurationManager.GetSetting("LiveIdOAuthSecretKey")));
app.UseTwitterAuthentication(
consumerKey: decryptor.Decrypt(CloudConfigurationManager.GetSetting("TwitterOAuthAppId")),
consumerSecret: decryptor.Decrypt(CloudConfigurationManager.GetSetting("TwitterOAuthSecretKey")));
app.UseFacebookAuthentication(
appId: decryptor.Decrypt(CloudConfigurationManager.GetSetting("FacebookOAuthAppId")),
appSecret: decryptor.Decrypt(CloudConfigurationManager.GetSetting("FacebookOAuthSecretKey")));
app.UseGoogleAuthentication(
clientId: decryptor.Decrypt(CloudConfigurationManager.GetSetting("GoogleOAuthAppId")),
clientSecret: decryptor.Decrypt(CloudConfigurationManager.GetSetting("GoogleOAuthSecretKey")));
}
Here's my Web API ConfigAuth:
app.SetDefaultSignInAsAuthenticationType(DefaultAuthenticationTypes.ExternalCookie);
// Enable the application to use a cookie to store information for the signed in user
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
CookieDomain = CloudConfigurationManager.GetSetting("AuthCookieDomain"),
CookieSecure = CookieSecureOption.Always,
//CookiePath = "/",
CookieHttpOnly = true,
ExpireTimeSpan = TimeSpan.FromDays(365),
SlidingExpiration = true,
Provider = new CookieAuthenticationProvider
{
OnApplyRedirect = ApplyRedirect
},
});
//app.UseExternalSignInCookie(DefaultAuthenticationTypes.ApplicationCookie);
// Uncomment the following lines to enable logging in with third party login providers
app.UseMicrosoftAccountAuthentication(
clientId: decryptor.Decrypt(CloudConfigurationManager.GetSetting("LiveIdOAuthAppId")),
clientSecret: decryptor.Decrypt(CloudConfigurationManager.GetSetting("LiveIdOAuthSecretKey")));
app.UseTwitterAuthentication(
consumerKey: decryptor.Decrypt(CloudConfigurationManager.GetSetting("TwitterOAuthAppId")),
consumerSecret: decryptor.Decrypt(CloudConfigurationManager.GetSetting("TwitterOAuthSecretKey")));
app.UseFacebookAuthentication(
appId: decryptor.Decrypt(CloudConfigurationManager.GetSetting("FacebookOAuthAppId")),
appSecret: decryptor.Decrypt(CloudConfigurationManager.GetSetting("FacebookOAuthSecretKey")));
app.UseGoogleAuthentication(
clientId: decryptor.Decrypt(CloudConfigurationManager.GetSetting("GoogleOAuthAppId")),
clientSecret: decryptor.Decrypt(CloudConfigurationManager.GetSetting("GoogleOAuthSecretKey")));
var config = GlobalConfiguration.Configuration;
config.EnableCors(new EnableCorsAttribute("*", "*", "*"));
Furthermore, a global handler has been enabled on the Web API to auth every call. (inherits from DelegatingHandler, checks Headers for API Key if it exists or calls AssertAuth() otherwise)
Well I've composed a tutorial covering the scenario you want to achieve, the tutorial is cookie-less and it depends only on bearer tokens which is the right way when you want to build SPA, you can check the series of posts here http://bitoftech.net/2014/06/01/token-based-authentication-asp-net-web-api-2-owin-asp-net-identity/
Related
It states here
that going forward, we must prefer the auth code flow method since "With the plans for third party cookies to be removed from browsers, the implicit grant flow is no longer a suitable authentication method."
I have set up an asp.net mvc 4 web application according to the sample app here. It works when I set up my app registration in AD B2C directory for Access Tokens (used for implicit grant flows). If I switch to "ID Tokens (for implicit and hybrid flows)" as the documentation recommends, I get error that my application is not setup for it.
As I understand from the documentation, I would have to specifiy separate endpoins for /authorize and/token to fetch a token after authorization. I am not sure from looking at the sample though how exactly I can do this. Below is the ConfigureAuth method as you can see in the sample code on github link provided:
public void ConfigureAuth(IAppBuilder app)
{
// Required for Azure webapps, as by default they force TLS 1.2 and this project attempts 1.0
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
// ASP.NET web host compatible cookie manager
CookieManager = new SystemWebChunkingCookieManager()
});
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
// Generate the metadata address using the tenant and policy information
MetadataAddress = String.Format(WellKnownMetadata, Tenant, DefaultPolicy),
// These are standard OpenID Connect parameters, with values pulled from web.config
ClientId = ClientId,
RedirectUri = RedirectUri,
PostLogoutRedirectUri = RedirectUri,
// Specify the callbacks for each type of notifications
Notifications = new OpenIdConnectAuthenticationNotifications
{
RedirectToIdentityProvider = OnRedirectToIdentityProvider,
AuthorizationCodeReceived = OnAuthorizationCodeReceived,
AuthenticationFailed = OnAuthenticationFailed,
},
// Specify the claim type that specifies the Name property.
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
ValidateIssuer = false
},
// Specify the scope by appending all of the scopes requested into one string (separated by a blank space)
Scope = $"openid profile offline_access {ReadTasksScope} {WriteTasksScope}",
// ASP.NET web host compatible cookie manager
CookieManager = new SystemWebCookieManager()
}
);
}
In my application I use MVC and Web.API.
The MVC portion handles the admin front-end, serving the cshtml pages, communication with the back-end, regular authentication using cookies, etc:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login")
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
The Web.API one handles the REST requests made by the iOS and Android applications. For that one I want to use token based authentication:
var oAuthServerOptions = new OAuthAuthorizationServerOptions
{
AllowInsecureHttp = true, //todo-err: change in prod
TokenEndpointPath = new PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = new SimpleAuthorizationServerProvider()
};
// Token Generation
app.UseOAuthAuthorizationServer(oAuthServerOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
My question is, what do I need to make this work? Regular and token authentication. Will I need to create a custom AuthorizeAttribute for the API controllers?
I appreciate any help :)
Your SimpleAuthorizationServerProvider needs to be implemented.
Generating a WebAPI project in VS2015 with authentication gives you something like this.
The article Applying Cookie-Stored Sessions With ASP.NET and OpenID Connect 1.0 by Kelvin Amorim is very useful for understanding multiple factors for supporting multiple authentication middlewares.
Some main points are:
Each authentication options middleware should use a different AuthenticationType (it is just a string key and there are some defaults to choose from)
You can set the cookie path and use corresponding MVC Areas (see RouteAreaAttribute) to control which cookies are used for which requests
I have a self-hosted OWIN application configured as an authorization server and a signalr resource server.
My clients are successfully obtaining the bearer token and presenting it for authorization in subsequent calls to the signalR hub.
My next step is to decouple the authorization service so that it can run on its own host. To get started I created a separate self-hosted app that contains only the authorization service code. It's still all in one solution on my development machine, but the authorization service and signalR resources are hosted in separate processes.
The auth flow is still working properly. The token is getting to my resource server, but now getting 401 unauthorized from the signalR hub.
There is alot of support out there for solving this in ASP.Net Web API, in which you would sync up a machine.config value in your web.config files. But that is not my architecture. Running as a self-hosted app under HttpListener uses different encryption, DPAPI by default.
There doesn't seem to be much discussion out there about solving this in a self-hosted architecture. My theory is that even under different processes on the same machine, the DPAPI decryption is failing and so I get 401.
I'm trying to figure out if there is some minimal approach to solving this or if I have to completely refactor maybe to use JWT instead.
EDIT: adding some code to help display my setup
public void ConfigureOAuth(IAppBuilder app)
{
OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = false,
TokenEndpointPath = new PathString("/account/login"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = new SimpleAuthorizationServerProvider()
};
app.UseOAuthAuthorizationServer(OAuthServerOptions);
}
public void ConfigureOAuth(IAppBuilder app)
{
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions
{
Provider = new ApplicationOAuthBearerAuthenticationProvider(),
});
}
Posting my own solution hopefully to help someone else down the road.
I did decide to implement a JWT solution rather than use the default. I think this is the better architecture anyway, decoupling your token encryption from the OS. I used this tutorial http://bitoftech.net/2014/10/27/json-web-token-asp-net-web-api-2-jwt-owin-authorization-server/
The crucial bits were creating your custom OAuthAuthorizationServerProvider and ISecureDataFormat for encrypting the token as shown in the tutorial. This just shows the OWIN config.
public void ConfigureOAuth(IAppBuilder app)
{
OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = false,
TokenEndpointPath = new PathString("/account/login"),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(30),
Provider = new JwtAuthorizationServerProvider(),
AccessTokenFormat = new CustomJwtFormat("https://foo.test.com")
};
app.UseOAuthAuthorizationServer(OAuthServerOptions);
}
Another issue you might face is getting the token to SignalR, where setting the Authorization header is not as straight forward as you might think. As it happens the cookie based implementation in this tutorial worked beautifully with JWT as well! http://blog.marcinbudny.com/2014/05/authentication-with-signalr-and-oauth.html#.VmWgMXarSCd
Again here is the OWIN config example.
public void ConfigureOAuth(IAppBuilder app)
{
//app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions
//{
// Provider = new ApplicationOAuthBearerAuthenticationProvider()
//});
var issuer = "https://foo.test.com";
var audience = "client_id";
var secret = TextEncodings.Base64Url.Decode("ABCDEF");
// Api controllers with an [Authorize] attribute will be validated with JWT
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
AllowedAudiences = new[] { audience },
IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
{
new SymmetricKeyIssuerSecurityTokenProvider(issuer, secret)
},
Provider = new ApplicationOAuthBearerAuthenticationProvider()
});
}
FWIW, consider that self-hosted OWIN authorization servers use DPAPI protection, but ASP.NET apps default to MachineKey data protection.
If you need to make those two collaborate, in the OWIN config you can specify a provider like this:
app.SetDataProtectionProvider(new DpapiDataProtectionProvider("myApp"));
Just make sure to add it in both Configuration methods for the IAppBuilder (Both projects)
HTH
I am using OWIN middleware for cookie authentication and openIdConnect. Before I added openIdConnect authentication to my startup auth code the cookie authentication option, LoginPath was used as the destination for redirecting unauthenticated users. This worked really well and is the functionality I would like to keep.
However, when I added app.UseOpenIdConnectAuthentication to my project, it started automatically redirecting unauthenticated users to my OpenIdConnect Authority (https://login.windows.net/).
Is there a way I can disable OpenIdConnectAuthentication setting the redirect path for unauthenticated users and rely on the LoginPath set for cookie authentication? My current work around is to manually set the redirect path in my authorize attribute, but I would like to let OWIN middleware handle this if possible.
Thanks.
Code:
public void ConfigureAuth(IAppBuilder app)
{
// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
var cookieOptions = new CookieAuthenticationOptions();
cookieOptions.AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie;
cookieOptions.LoginPath = new PathString("/Account/Login");
app.SetDefaultSignInAsAuthenticationType(cookieOptions.AuthenticationType);
app.UseCookieAuthentication(cookieOptions);
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
AuthenticationType = FranchiseAuthType,
ClientId = franchiseClientId,
Authority = FranchiseAuthority,
PostLogoutRedirectUri = postLogoutRedirectUri,
});
}
I'm not sure if you were able to resolve this issue, but what you want to do it is add
AuthenticationMode = AuthenticationMode.Passive
To your authentication options. This will make the OpenIdConnect authentication rely solely on your code to make calls to it. I believe this is what you intend to happen.
So your new code should look like this:
public void ConfigureAuth(IAppBuilder app)
{
// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
var cookieOptions = new CookieAuthenticationOptions();
cookieOptions.AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie;
cookieOptions.LoginPath = new PathString("/Account/Login");
app.SetDefaultSignInAsAuthenticationType(cookieOptions.AuthenticationType);
app.UseCookieAuthentication(cookieOptions);
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
AuthenticationType = FranchiseAuthType,
AuthenticationMode = AuthenticationMode.Passive,
ClientId = franchiseClientId,
Authority = FranchiseAuthority,
PostLogoutRedirectUri = postLogoutRedirectUri,
});
}
EDIT: This looked like it fixed my problems but it caused a more serious issue than the one it fixed. If I set use cookies after use open Id connect then my openidconnect notifications will have a null sessions HttpContext.Current.Session and after authentication my authentication result is not stored in the cookie.
Whichever authentication is added last becomes the authoritative source for setting the redirect path for unauthenticated uses.
By moving
app.UseCookieAuthentication();
After app.UseOpenIdConnectAuthentication() the desired behavior was achieved.
public void ConfigureAuth(IAppBuilder app)
{
// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
var cookieOptions = new CookieAuthenticationOptions();
cookieOptions.AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie;
cookieOptions.LoginPath = new PathString("/Account/Login");
app.SetDefaultSignInAsAuthenticationType(cookieOptions.AuthenticationType);
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
AuthenticationType = FranchiseAuthType,
ClientId = franchiseClientId,
Authority = FranchiseAuthority,
PostLogoutRedirectUri = postLogoutRedirectUri,
});
//move this after UseOpenIdConnectAuthentication and the LoginPath
//value is used for redirecting unauthenticated users
app.UseCookieAuthentication(cookieOptions);
}
I have Owin configured to issue both a token and cookie upon authentication:
public void Configuration(IAppBuilder app)
{
var cookieOptions = new CookieAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
CookieHttpOnly = true, // JavaScript should use the Bearer
//AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
CookieName = "MyCookie",
LoginPath = new PathString("/app/index.html#/login"),
};
var oAuthServerOptions = new OAuthAuthorizationServerOptions
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = new MyAuthorizationServerProvider(),
};
var oAuthBearerOptions = new OAuthBearerAuthenticationOptions
{
};
// Must be registered in this order!
app.UseCookieAuthentication(cookieOptions);
app.UseOAuthAuthorizationServer(oAuthServerOptions);
app.UseOAuthBearerAuthentication(oAuthBearerOptions);
}
This works fine - it issues both the Bearer token for my SPA to call my API and the cookie so my old school MVC pages can be logged in too.
But if I register the OAuth server before declaring that I want to use CookieAuth, no cookie is issued. In other words if I do this, it doesn't work:
app.UseOAuthAuthorizationServer(oAuthServerOptions);
app.UseCookieAuthentication(cookieOptions);
app.UseOAuthBearerAuthentication(oAuthBearerOptions);
Also, if I uncomment this line, it also doesn't issue the cookie:
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
My question is why does the order of registration with Owin matter? And why does setting the AuthenticationType in the cookie as "ApplicationCookie" also make it fail?
I'm not familiar with the UseOAuthAuthorizationServer() middleware, but I assume it works the same as other external authentication middleware (e.g. the Google middleware).
An authentication middleware that redirects to an external source for authentication, will only be used once, at the start, of each browsing session. It will then defer the authentication of the up-coming requests to a cookie authentication that maintains the session. This is good, because it means the overhead of the external authentication is only done once for each session.
A middleware that wants to set a cookie does typically not do it itself. Instead it sets a property in the Owin context with an AuthenticationResponseGrant. The grant is then processed by the cookie middleware that extracts the identity and sets the cookie.
For this to work:
The cookie handler must be registered before the external authentication middleware in the pipeline.
The authentication type in the AuthenticationResponseGrant must match the type of the cookie middleware.
So changing the order of the registration violates 1. and excluding the authentication type violates 2.
I have written an in depth blog post about it if you want more details.