I'm trying to use the same authentication between the MVC controllers and the Web Api controllers. The Web api is in the same project, just in an /Controllers/API/ folder.
I can't seem to figure out how to authenticate using OWIN, when I logged in through MVC and created a claim and a cookie like the example below.
var identity = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Name,"Admin"),
new Claim(ClaimTypes.Role,"Administrator")
, "ApplicationCookie");
var ctx = Request.GetOwinContext();
var authManager = ctx.Authentication;
authManager.SignIn(identity);
return RedirectToAction("Index", "Home", null);
}
Everything works fine in the MVC controllers, but I can't use the [Authorize(Roles="Administrator"] attribute on my web API controller and have it work correctly. It always lets me through regardless.
Thanks
EDIT: Only way I've been able to combat this is having a static class and property store the IPrincipal and then when Overriding the Authorize Attribute, look for that property and check if the Role is there that way. Which im not sure if that is a good idea or not?
Where is your authentication code written ? MVC Controller or Web API Controller ? I would recommend to have it in your web API controller that way you can later use it for any other application (SPA or any other web application).You need to build a Authorization server/Resource Server model (sorry for my english wasn't sure how to frame this sentence). In your case Web API being both and MVC site being a resource server.
Below is a sample for JWT + Cookie middleware
Build a authorization server using JWT with WEB API and ASP.Net Identity as explained here http://bitoftech.net/2015/02/16/implement-oauth-json-web-tokens-authentication-in-asp-net-web-api-and-identity-2/
once you do that your webAPIs startup.cs will look like below
/// Configures cookie auth for web apps and JWT for SPA,Mobile apps
private void ConfigureOAuthTokenGeneration(IAppBuilder app)
{
// Configure the db context, user manager and role manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationRoleManager>(ApplicationRoleManager.Create);
//Cookie for old school MVC application
var cookieOptions = new CookieAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
CookieHttpOnly = true, // JavaScript should use the Bearer
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/api/Account/Login"),
CookieName = "AuthCookie"
};
// Plugin the OAuth bearer JSON Web Token tokens generation and Consumption will be here
app.UseCookieAuthentication(new CookieAuthenticationOptions());
OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
//For Dev enviroment only (on production should be AllowInsecureHttp = false)
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/oauth/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(30),
Provider = new CustomOAuthProvider(),
AccessTokenFormat = new CustomJwtFormat(ConfigurationManager.AppSettings["JWTPath"])
};
// OAuth 2.0 Bearer Access Token Generation
app.UseOAuthAuthorizationServer(OAuthServerOptions);
}
You can find CustomOAuthProvider,CustomJwtFormat classes here https://github.com/tjoudeh/AspNetIdentity.WebApi/tree/master/AspNetIdentity.WebApi/Providers
In your MVC app add below in startup.cs
public void Configuration(IAppBuilder app)
{
ConfigureOAuthTokenConsumption(app);
}
private void ConfigureOAuthTokenConsumption(IAppBuilder app)
{
var issuer = ConfigurationManager.AppSettings["AuthIssuer"];
string audienceid = ConfigurationManager.AppSettings["AudienceId"];
byte[] audiencesecret = TextEncodings.Base64Url.Decode(ConfigurationManager.AppSettings["AudienceSecret"]);
app.UseCookieAuthentication(new CookieAuthenticationOptions { CookieName = "AuthCookie" , AuthenticationType=DefaultAuthenticationTypes.ApplicationCookie });
//// Api controllers with an [Authorize] attribute will be validated with JWT
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Passive,
AuthenticationType = "JWT",
AllowedAudiences = new[] { audienceid },
IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
{
new SymmetricKeyIssuerSecurityTokenProvider(issuer, audiencesecret)
}
});
}
In your MVC controller when you receive the token de-serialize it and generate a cookie from the acceSs token
AccessClaims claimsToken = new AccessClaims();
claimsToken = JsonConvert.DeserializeObject<AccessClaims>(response.Content);
claimsToken.Cookie = response.Cookies[0].Value;
Request.Headers.Add("Authorization", "bearer " + claimsToken.access_token);
var ctx = Request.GetOwinContext();
var authenticateResult = await ctx.Authentication.AuthenticateAsync("JWT");
ctx.Authentication.SignOut("JWT");
var applicationCookieIdentity = new ClaimsIdentity(authenticateResult.Identity.Claims, DefaultAuthenticationTypes.ApplicationCookie);
ctx.Authentication.SignIn(applicationCookieIdentity);
With this a cookie will be created and [Authorize] attribute in MVC Site and WebAPI will honor this cookie.
Related
I have implemented a simple authorization server with C# using the following guide. This works fine for my other applications that I built.
The problem arises when I want to login to the Auth server itself to manage user settings like 'change password' or 'change email'. I want to show a front-end, but when I use the [Authorize] tag on the endpoint, my incoming User is always null, while it works fine on the other applications.
I tried setting the DefaultAuthenticationType to different values but to no avail.
How is it possible to log in to the Auth server with the same token it generates?
EDIT:
To make the flow more obvious:
I have a website, api and an authorization server.
When users want to login to the website, they redirect to the authorization server to authenticate and authorize. This is through the oauth2.0 authorization code grant flow. The access token is then used to retrieve information from the api that is protected with the [Authorize] tag. This works.
What I want now is that I can redirect to a specific 'change password' view on the authorization server itself, and this has to be protected with [Authorize] as well. This does not work, as the user will be null.
EDIT2: The Authorize tag automatically protects api/view endpoints in c# aspnet mvc/api.
Edit3: This is how I configure my Auth server.
public void ConfigureOAuth(IAppBuilder app, IUnityContainer container)
{
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
AuthenticationMode = AuthenticationMode.Passive,
LoginPath = new PathString("/Account/Login"),
LogoutPath = new PathString("/Account/Logout"),
Provider = new CookieAuthenticationProvider()
{
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationIdentityUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) => manager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie))
}
});
OAuthBearerOptions = new OAuthBearerAuthenticationOptions();
OAuthBearerServerOptions = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/token"),
AuthorizeEndpointPath = new PathString("/authorize"),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(10),
Provider = container.Resolve<SimpleAuthorizationServerProvider>(),
RefreshTokenProvider = container.Resolve<SimpleRefreshTokenProvider>(),
AuthorizationCodeProvider = container.Resolve<SimpleAuthorizationCodeProvider>()
};
// Token Generation
app.UseOAuthAuthorizationServer(OAuthBearerServerOptions);
app.UseOAuthBearerAuthentication(OAuthBearerOptions);
var configuration = container.Resolve<IConfiguration>().Get<AuthServiceSettings>("Application");
// Configure Google
GoogleAuthOptions = new GoogleOAuth2AuthenticationOptions()
{
ClientId = configuration.GoogleSettings.ClientId,
ClientSecret = configuration.GoogleSettings.ClientSecret,
Provider = new GoogleAuthProvider()
};
GoogleAuthOptions.Scope.Add("email");
GoogleAuthOptions.Scope.Add("openid");
app.UseGoogleAuthentication(GoogleAuthOptions);
//Configure Facebook External Login
FacebookAuthOptions = new FacebookAuthenticationOptions()
{
AppId = configuration.FacebookSettings.AppId,
AppSecret = configuration.FacebookSettings.AppSecret,
Provider = new FacebookAuthProvider(),
BackchannelHttpHandler = new FacebookBackChannelHandler(),
UserInformationEndpoint = configuration.FacebookSettings.UserInformationEndpoint
};
FacebookAuthOptions.Scope.Add("email");
FacebookAuthOptions.Scope.Add("public_profile");
app.UseFacebookAuthentication(FacebookAuthOptions);
}
EDIT4: Debug information when checking variables:
>> HttpContext.User.Identity.IsAuthenticated
false
>> System.Threading.Thread.CurrentPrincipal.Identity.IsAuthenticated
true
No idea why these identities are not the same... but based on this I could write my own authorize tag and use that, but I'm reluctant to use something from Thread because I don't know if this works on multiple threads.
I am having a lot of trouble understanding some things in Asp.NET Core. I already have a Asp.NET 4.5 application that has login authentication using FormAuthenticationTicket but my goal is to set up a Core Web Api that authenticates a user and creates a cookie for my 4.5 Application to read, and on redirect to already be signed in via cookie.
I have given both applications the same <machinekey> in the web.config and added UseCookieAuthentication with CookieAuthenticationOptions to Startup.cs but I am at a loss from here on how to replicate the FormsAuthenticationTicket inside my ApplicationController.cs in my Core application. I find that the documentation for Core is not overly consistant yet but I have been trying out a lot of suggestions to no avail.
I think the main confusion for me is that I can create a cookie in Core I am clearly not creating it correctly or most likely not authenticating correctly either.
Startup.cs in Configure function
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme = "ApiAuth",
CookieName = ".ASPXAUTH",
CookieHttpOnly = false,
ExpireTimeSpan = TimeSpan.FromDays(30),
SlidingExpiration = true,
AutomaticAuthenticate = true,
LoginPath = new PathString("/Application/Authorize"),
});
ApplicationController.cs
[HttpGet("Authorize/{appGuid}/{userGuid}", Name = "SignIn")]
public async Task<IActionResult> SignIn(Guid appGuid, Guid userGuid)
{
var application = Application.Find(appGuid);
var user = User.Find(userGuid);
if (application != null && user != null)
{
await HttpContext.Authentication.SignOutAsync("ApiAuth");
/****************Confusion start****************/
Claim cookiePath = new Claim(ClaimTypes.CookiePath, ".ASPXAUTH");
Claim expiration = new Claim(ClaimTypes.Expiration, DateTime.UtcNow.AddDays(30).ToString());
Claim expiryDate = new Claim(ClaimTypes.Expired, "false");
Claim persistant = new Claim(ClaimTypes.IsPersistent, "true");
Claim issueDate = new Claim("IssueDate", DateTime.UtcNow.ToString());
Claim name = new Claim(ClaimTypes.Name, user.Username);
Claim userData = new Claim(ClaimTypes.UserData, "");
Claim version = new Claim(ClaimTypes.Version, "2");
ClaimsPrincipal principal = new ClaimsPrincipal(new ClaimsIdentity(new[] { cookiePath, expiration, expiryDate,
persistant, issueDate, name, userData, version }, "ApiAuth"));
await HttpContext.Authentication.SignInAsync("ApiAuth", principal);
/****************Confusion end****************/
return new RedirectResult("http://localhost/MyWebsite/Repository.aspx");
}
return Unauthorized();
}
The size of the cookie is much larger than the one on my 4.5 application and I am at a loss as to where to go from here. I believe I am also causing conflicting settings with UseCookieAuthentication and the ClaimsPrincipal.
I'm creating WEB-API application which had auto generated document feature work as MVC. And general end points as API controllers, initially API end points works fine with tokens authentications. But after I'm trying to do MVC controllers authenticated using a cookie mode, it working as expected but API controllers fails "Authorization has been denied".
In Startup.cs
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
var auth = ConfigurationManager.AppSettings["AuthAuthority"];
JwtSecurityTokenHandler.InboundClaimTypeMap = new Dictionary<String, String>();
var tokenOptions = new IdentityServerBearerTokenAuthenticationOptions();
tokenOptions.Authority = auth;
tokenOptions.PreserveAccessToken = true;
//adding for Outh2 authentication
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Cookies"
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
Authority = "https://localhost:44300/",
ClientId = "mvc_Doc",
RedirectUri = "http://localhost:64741/Help",
ResponseType = "id_token token",
SignInAsAuthenticationType = "Cookies",
UseTokenLifetime = false,
Scope = "openid profile",
});
app.UseIdentityServerBearerTokenAuthentication(tokenOptions);
app.UseStageMarker(PipelineStage.Authenticate);
How would I handle this?
I am trying to combine OpenId authentication and bearer token authentication through the use of IdentityServer.
My project is an Asp.net MVC project, with a web api component. I would like to use OpenId authentication for the standard MVC controllers, and bearer token authentication for the web api component.
My front end is written in Angularjs, and calls web api methods through the $http service to populate the UI.
I am having a problem setting up the authentication workflow.
My thoughts initially were to try to execute the following algorithm:
User requests some web api method from the server through an ajax call ($http)
App detects the user is not authenticated and bearer token is not present in request header, redirects to MVC controller method to retrieve the bearer token.
Since the user is not authenticated, the server will redirect to the IdentityServer default login page.
Once the user is logged in, the server automatically redirects back to the bearer token method to get a bearer token
The generated bearer token is somehow returned to the client.
However my algorithm breaks down at step 3. I am trying to implement step 2 by using an httpinterceptor to inspect outgoing request to determine if a bearer token is present.
app.factory('authInterceptorService', ['$q', '$location', 'localStorageService', function ($q, $location, localStorageService) {
var authInterceptorServiceFactory = {};
var _request = function (config) {
config.headers = config.headers || {};
var authData = localStorageService.get('authorizationData');
if (authData) {
config.headers.Authorization = 'Bearer ' + authData.token;
}
else {
$location.path('/Authentication/BearerToken');
}
return config;
}
var _responseError = function (rejection) {
if (rejection.status === 401) {
$location.path('/Authentication/BearerToken');
}
return $q.reject(rejection);
}
authInterceptorServiceFactory.request = _request;
authInterceptorServiceFactory.responseError = _responseError;
return authInterceptorServiceFactory;}]);
If the token is not present, I exit the SPA and redirect to the bearer token MVC method.
This will successfully redirect to the login page, however once I log in, I am returned a bearer token in JSON directly on the page, since I am now outside of the Ajax call.
So my question is
could you please provide me with alternate ideas (and an outline of an implementation) on how to combine these two modes of authentication into one workflow?
Perhaps there is a way to customize IdentityServer to do what I want?
My basic requirement is that an unauthenticated user on my SPA angularjs app is redirected to the default IdentityServer login page, and once logged in, that the initial request be fulfilled.
Thanks in advance!
Just in case you are curious, my IdentityServer setup is as follows. The authentication service is in a separate project from the web application. Each has it's own Startup.cs file.
The MVC Startup.cs file
public class Startup
{
public void Configuration(IAppBuilder app)
{
AntiForgeryConfig.UniqueClaimTypeIdentifier = Thinktecture.IdentityServer.Core.Constants.ClaimTypes.Subject;
JwtSecurityTokenHandler.InboundClaimTypeMap = new Dictionary<string, string>();
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Cookies",
});
var openIdConfig = new OpenIdConnectAuthenticationOptions
{
Authority = "https://localhost:44301/identity",
ClientId = "baseballStats",
Scope = "openid profile roles baseballStatsApi",
RedirectUri = "https://localhost:44300/",
ResponseType = "id_token token",
SignInAsAuthenticationType = "Cookies",
UseTokenLifetime = false,
Notifications = new OpenIdConnectAuthenticationNotifications
{
SecurityTokenValidated = async n =>
{
var userInfoClient = new UserInfoClient(
new Uri(n.Options.Authority + "/connect/userinfo"),
n.ProtocolMessage.AccessToken);
var userInfo = await userInfoClient.GetAsync();
// create new identity and set name and role claim type
var nid = new ClaimsIdentity(
n.AuthenticationTicket.Identity.AuthenticationType,
Thinktecture.IdentityServer.Core.Constants.ClaimTypes.GivenName,
Thinktecture.IdentityServer.Core.Constants.ClaimTypes.Role);
userInfo.Claims.ToList().ForEach(c => nid.AddClaim(new Claim(c.Item1, c.Item2)));
// keep the id_token for logout
nid.AddClaim(new Claim("id_token", n.ProtocolMessage.IdToken));
// add access token for sample API
nid.AddClaim(new Claim("access_token", n.ProtocolMessage.AccessToken));
// keep track of access token expiration
nid.AddClaim(new Claim("expires_at", DateTimeOffset.Now.AddSeconds(int.Parse(n.ProtocolMessage.ExpiresIn)).ToString()));
// add some other app specific claim
nid.AddClaim(new Claim("app_specific", "some data"));
n.AuthenticationTicket = new AuthenticationTicket(
nid,
n.AuthenticationTicket.Properties);
}
}
};
app.UseOpenIdConnectAuthentication(openIdConfig);
app.UseResourceAuthorization(new AuthorizationManager());
app.Map("/api", inner =>
{
var bearerTokenOptions = new IdentityServerBearerTokenAuthenticationOptions
{
Authority = "https://localhost:44301/identity",
RequiredScopes = new[] { "baseballStatsApi" }
};
inner.UseIdentityServerBearerTokenAuthentication(bearerTokenOptions);
var config = new HttpConfiguration();
config.MapHttpAttributeRoutes();
inner.UseWebApi(config);
});
}
}
The IdentityServer Startup.cs
public class Startup
{
public void Configuration(IAppBuilder app)
{
var policy = new System.Web.Cors.CorsPolicy
{
AllowAnyOrigin = true,
AllowAnyHeader = true,
AllowAnyMethod = true,
SupportsCredentials = true
};
//policy.ExposedHeaders.Add("location: http://location.com");
policy.Headers.Add("location: testing");
app.UseCors(new CorsOptions
{
PolicyProvider = new CorsPolicyProvider
{
PolicyResolver = context => Task.FromResult(policy)
}
});
app.Map("/identity", idsrvApp =>
{
idsrvApp.UseIdentityServer(new IdentityServerOptions
{
SiteName = "Embedded IdentityServer",
SigningCertificate = LoadCertificate(),
Factory = InMemoryFactory.Create(
users: Users.Get(),
clients: Clients.Get(),
scopes: Scopes.Get())
});
});
}
X509Certificate2 LoadCertificate()
{
return new X509Certificate2(
string.Format(#"{0}\bin\Configuration\idsrv3test.pfx", AppDomain.CurrentDomain.BaseDirectory), "idsrv3test");
}
}
EDIT
I have made some progress in redirecting to the Login page. The issue I was having is that the initial call to the web api was being redirected by IdentityServer with a status code of 302. This meant that I could not get access to the Location header which contains the url I want to redirect to. So instead, I added some owin middleware to check the status code, and if a 302, return a 401 instead (I need to also check that the call is Ajax but have not yet implemented this).
app.Use(async (environment, next) =>
{
await next();
if (environment.Response.StatusCode == 302)
{
environment.Response.StatusCode = 401;
}
});
This then gives me access to the Location header on the client side, and I can redirect, like so:
getPlayerList: function (queryParameters) {
var deferred = $q.defer();
$http.post('api/pitchingstats/GetFilteredPlayers', {
skip: queryParameters.skip,
take: queryParameters.take,
orderby: queryParameters.orderby,
sortdirection: queryParameters.sortdirection,
filter: queryParameters.filter
}).success(function (data, status, headers, config) {
if (status === 401) {
window.location = headers().location;
}
deferred.resolve(data);
}).error(function (data, status, headers, config) {
deferred.reject(status);
});
I know it's a hack, but it's the only way I can make my approach work currently. By all means feel free to suggest other methods.
Following this guide for external auth using MVC 5 on Owin - External login providers with owinkatana.
I have added the following to my Owin Nancy application
Startup.cs -
app.Properties["Microsoft.Owin.Security.Constants.DefaultSignInAsAuthenticationType"] = "ExternalCookie";
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "ExternalCookie",
AuthenticationMode = Microsoft.Owin.Security.AuthenticationMode.Passive,
});
app.UseTwitterAuthentication(new TwitterAuthenticationOptions
{
ConsumerKey = "mykey",
ConsumerSecret = "mypass"
});
LoginModule.cs (nancy module)
Post["ExternalLogin"] = _ =>
{
var provider = Request.Form.name;
var auth = Context.GetAuthenticationManager();
auth.Challenge(new AuthenticationProperties
{
RedirectUri = String.Format("/?provder={0}", provider)
}, provider);
return HttpStatusCode.Unauthorized;
};
Now at the challenge point here nothing happens whatsoever. It just shows a blank page with the Url of the redirect. I have confirmed that I can get it to work following the example in MVC.
Does anyone know the correct Nancy code for this section?
I'll expand on a comment I was about to leave and just make it an answer (even though you moved away from Nancy it seems). I asked a similar question, and was pointed to the following code example on github:
https://github.com/aspnet-contrib/AspNet.Security.OpenIdConnect.Server/tree/dev/samples/Nancy/Nancy.Client
Assuming you have your OIDC wired up properly in Startup.cs, the following code is what I needed to get Nancy module to trigger the authentication on my signin/signout routes:
namespace Nancy.Client.Modules {
public class AuthenticationModule : NancyModule {
public AuthenticationModule() {
Get["/signin"] = parameters => {
var manager = Context.GetAuthenticationManager();
if (manager == null) {
throw new NotSupportedException("An OWIN authentication manager cannot be extracted from NancyContext");
}
var properties = new AuthenticationProperties {
RedirectUri = "/"
};
// Instruct the OIDC client middleware to redirect the user agent to the identity provider.
// Note: the authenticationType parameter must match the value configured in Startup.cs
manager.Challenge(properties, OpenIdConnectAuthenticationDefaults.AuthenticationType);
return HttpStatusCode.Unauthorized;
};
Get["/signout"] = Post["/signout"] = parameters => {
var manager = Context.GetAuthenticationManager();
if (manager == null) {
throw new NotSupportedException("An OWIN authentication manager cannot be extracted from NancyContext");
}
// Instruct the cookies middleware to delete the local cookie created when the user agent
// is redirected from the identity provider after a successful authorization flow.
manager.SignOut("ClientCookie");
// Instruct the OpenID Connect middleware to redirect
// the user agent to the identity provider to sign out.
manager.SignOut(OpenIdConnectAuthenticationDefaults.AuthenticationType);
return HttpStatusCode.OK;
};
}
}
}
Code source: https://github.com/aspnet-contrib/AspNet.Security.OpenIdConnect.Server/blob/dev/samples/Nancy/Nancy.Client/Modules/AuthenticationModule.cs
Hope that helps!