I'm bulding a web api with owin and JWT, when I execute the project it shows me an exception. This is a method that consume the JWT, I'm having problems with as:AudienceId and as:AudienceSecret, it tells me that AudienceSecret is null. What can be the issue?
private void ConfigureOAuthTokenConsumption(IAppBuilder app)
{
var issuer = "http://localhost:59822";
string audienceId = ConfigurationManager.AppSettings["as:AudienceId"];
byte[] audienceSecret = TextEncodings.Base64Url.Decode(ConfigurationManager.AppSettings["as:AudienceSecret"]);
// Api controllers with an [Authorize] attribute will be validated with JWT
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
AllowedAudiences = new[] { audienceId },
IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
{
new SymmetricKeyIssuerSecurityTokenProvider(issuer, audienceSecret)
}
});
}
This statement is looking for the appSettings key as:AudienceSecret in web.config file.
byte[] audienceSecret = TextEncodings.Base64Url.Decode(ConfigurationManager.AppSettings["as:AudienceSecret"]);
Since the result audienceSecret is null. The statement is not finding the key as:AudienceSecret. The most reasonable explanation is the key is missing from web.config. You need to add the key to web.config like this.Something like this
<appSettings>
<add key="as:AudienceId" value="414e1927a3884f68abc79f7283837fd1" />
<add key="as:AudienceSecret" value="qMCdFDQuF23RV1Y-1Gq9L3cF3VmuFwVbam4fMTdAfpo" />
</appSettings>
You may want to use different values cause it's ... a secret.
Related
I have been working on a problem for about a month now in regards to JWT authentication. I have multiple Asp.net MVC 4.5 applications using Owin that are deployed on the same machine and have different subdomains using IIS with an integrated pipeline. My problem is that when I log in to one application and browse to another application it does not re-authenticate and automatically allows the user to pass the authorization filter.
I have tried setting the cookie policy to strict and the cookie domain, path and name to different values and am unable to resolve this problem.
I have one custom Authentication Library that each of these applications use. I hope you can help.
What I would like to happen is that each application maintain its own authentication session and just because the user authenticated against the AD domain, it requires the user to reauthorize to make sure they have access to a particular application.
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
CookieName = $"{tenantId}{clientId}"
})
.UseOpenIdConnectAuthentication(GetAuthenticationOptions(clientId, tenantId, clientSecret));
private static OpenIdConnectAuthenticationOptions GetAuthenticationOptions(string clientId, string tenantId, string clientSecret)
{
return new OpenIdConnectAuthenticationOptions
{
MetadataAddress = string.Format("https://login.microsoftonline.com/{0}/.well-known/openid-configuration", tenantId),
ClientId = clientId,
ClientSecret = clientSecret,
ResponseType = "id_token token",
Resource = "https://graph.microsoft.com",
UseTokenLifetime = true,
AuthenticationMode = AuthenticationMode.Active,
Notifications = new OpenIdConnectAuthenticationNotifications
{
SecurityTokenValidated = SecurityTokenValidated
},
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "mailNickName",
}
};
}
Try to set the "cookieName" attribute of the "sessionState" XML element in your web.config to different values for each of your applications. That will keep them from using the same session ID.
<system.web>
<sessionState regenerateExpiredSessionId="false" cookieless="UseCookies" cookieName="id" />
</system.web>
sessionState Element
I have an ASP.NET Core 2.1 WebApi, in which I have implemented JWT authentication. The user calls api/authentication/authenticate, passes their username/password in the message body, and gets back a JWT in return which they then use to access the service.
I also need the API to accept Windows authentication -- the user will call api/authentication/windows passing no user information, the service will check they are in the list of authorized users as listed in the web.config file (if I am hosting in IIS). If so, return a JWT token and the user can use that to access the service.
Currently I'm thinking about this...
The api/authentication/windows method will get the username from the request
Check the username against the list of authorized users. If they are on it, return a token. If not, go to (3)
Check against any groups in the authorized users list. If they are a member, return a token. If not, return a 401 Unauthorized error
Is this the correct way to approach this?
Very similar (unanswered) question here: Generate JWT token on successful authentication with Windows Authentication
If you want to enable both JWT and AD authentication ,in my option, you still need to validate the user's credential(username/password) against Active Directory in web api :
https://www.brechtbaekelandt.net/blog/post/authenticating-against-active-directory-with-aspnet-core-2-and-managing-users
Pass just username won't work since there is no authenticated user context in web api .
After validating user credential , you can generate jwt token as usual , for example if using HS256:
private string BuildToken()
{
var claims = new[] {
new Claim(JwtRegisteredClaimNames.NameId,"name1"),
new Claim(JwtRegisteredClaimNames.Sub,"name1"),
new Claim("customer","customer1"),
new Claim(JwtRegisteredClaimNames.Email,"wuxiyuan#sina,com"),
new Claim("role","user"),
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("Youkey"));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken("name1",
"name1",
claims,
expires: DateTime.Now.AddDays(1),
signingCredentials: creds);
return new JwtSecurityTokenHandler().WriteToken(token);
}
To get the username from the request to the route api/authentication/windows you should activate windows authentication for the asp.net core application. You can achieve that either modifying the web.config or enable the windows authentication in IIS.
<configuration>
<system.webServer>
<security>
<authentication>
<anonymousAuthentication enabled="true" />
<windowsAuthentication enabled="true" />
</authentication>
</security>
</system.webServer>
</configuration>
For debugging purposes modify launchSettings.json:
"iisSettings": {
"windowsAuthentication": true,
}
Leave the anonymous authentication activated: <anonymousAuthentication enabled="true" />. It is necessary in order the JWT authentication works properly for the route api/authentication/authenticate
Make sure that the attribute forwardWindowsAuthToken of the aspNetCore element in web.config is not deactivated: forwardWindowsAuthToken="true" or remove it because of the default value (true)
Add IISIntegration to the webHostBuilder unless you use a default builder: WebHost.CreateDefaultBuilder(args) - UseIISIntegration is called implicit within this extension method.
Add an Authorize attribute for the POST-method which will be mapped with the route api/authentication/windows
Test the authentication (sending windows-credentials):
var handler = new System.Net.Http.HttpClientHandler()
{
Credentials = System.Net.CredentialCache.DefaultCredentials
};
var httpClient = new System.Net.Http.HttpClient(handler)
{
BaseAddress = new Uri("http://localhost")
};
var response = await httpClient.PostAsync("api/authentication/windows", null);
or using XMLHttpRequest object:
var xhr = new XMLHttpRequest();
xhr.open('POST', 'http://localhost/api/authentication/windows', true);
xhr.withCredentials = true;
xhr.send();
Get the user name in the controller:
var username = HttpContext.User.FindFirst(System.Security.Claims.ClaimTypes.Name)?.Value;
Generate a JWT-Token, e.g using jose-jwt:
var claims = new Dictionary<string, object>
{
["jti"] = Guid.NewGuid(),
["sub"] = username,
["exp"] = DateTimeOffset.UtcNow.AddMinutes(100).ToUnixTimeSeconds()
};
var secretKey = new byte[] { 164, 60, 194, 0, 161 };
var headers = new Dictionary<string, object>
{
["alg"] = "HS512",
["typ"] = "JWT"
};
var token = JWT.Encode(claims, secretKey, JwsAlgorithm.HS512, headers);
I have an api implemented under an ApiController in an MVC (Asp.net 4.5.2). In that api, I want to throw a HttpResponseException with a HttpResponseMessage(HttpStatusCode.Unauthorized) and specify a ReasonPhrase. How can this be sent directly to the client rather than having asp/mvc try to redirect them to a login page?
var message = new HttpResponseMessage(HttpStatusCode.Unauthorized);
message.ReasonPhrase = "Hello";
throw new HttpResponseException(message);
But redirect depends on Web.config settings. I think you have authentication section in web.config somethink like this:
<system.web>
<authentication mode="Forms">
<forms loginUrl="/Login/Index"></forms>
</authentication>
</system.web>
If you delete this section, redirection won't happen. But in this case you should implement authentication on your own.
Asp.Net Form Authentication module converts 401 to 302
If you using UseCookieAuthentication, then suppress this by changing OnApplyRedirect
File Startup.Auth.cs --> ConfigureAuth method --> inside app.UseCookieAuthentication(new CookieAuthenticationOptions { Provider = new CookieAuthenticationProvider { --> Add OnApplyRedirect
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
Provider = new CookieAuthenticationProvider
{
OnApplyRedirect = context =>
{
if (!context.Request.Uri.LocalPath.StartsWith(VirtualPathUtility.ToAbsolute("~/api")))
{
context.Response.Redirect(context.RedirectUri);
}
}
}
});
I've had quite the challenge in handling the following scenario.
I want to use the Unity DI Framework to create a new channel for my service when needed.
The service is secured with Federated security.
The service is not called from within a service hosted within IIS, but called from within a self-hosted WCF service.
My current issue is occurring in step 3 above - how do I bootstrap the token and yet then satisfy the above 2 requirements also?? I'll outline the solutions to each step though as they're non-trivial and will hopefully assist someone else with this issue.
Solving issue 1:
The following code-snippet will create a new instance of the channel anytime one is required.
container.RegisterType<IMyWcfService>(new PerResolveLifetimeManager(),
new InjectionFactory(
x => new ChannelFactoryWithChannelFactoryOperations<IMyWcfService>("*")
.CreateChannel()));
Here's some examples of people doing this better than I:
http://unity.codeplex.com/discussions/211736
https://gist.github.com/tracker1/5675161
You can also use alternative lifetime managers as well e.g. the TransientLifetimeManager would work well here.
Solving Issue 2:
Now the real difficulty begins - how do I include a security token with the InjectionFactory?
Clearly I'm going to want to use CreatChannelWithIssuedToken, but I'm going to need to grab the bootstrap token to do so. This is fairly well documented around the net, e.g. here:
http://www.cloudidentity.com/blog/2012/11/30/using-the-bootstrapcontext-property-in-net-4-5-2/
Some important things to note: make sure that you have a serviceBehaviors section in the config that specifies useIdentityConfiguration="true" otherwise your system.identityModel section will be ignored e.g.
<serviceBehaviors>
<behavior name="">
<serviceCredentials useIdentityConfiguration="true" />
</behavior>
</serviceBehaviors>
And then your system.identityModel section should also feature:
<system.identityModel>
<identityConfiguration>
<securityTokenHandlers>
<securityTokenHandlerConfiguration saveBootstrapContext="true" />
</securityTokenHandlers>
</identityConfiguration>
</system.identityModel>
Then, provided that you've made a request to set this correctly on the session (see my other question: AJAX call against REST endpoint secured with Thinktecture's IdentityServer STS it will be available in the session whenever you access the bootstrap context security token like so:
var bootstrapContext = ((ClaimsIdentity) Thread.CurrentPrincipal.Identity).BootstrapContext
as BootstrapContext;
SecurityToken securityToken = bootstrapContext.SecurityToken;
You can then either change your InjectionFactory to look like this:
container.RegisterType<IControllerConfigurationService>(new PerResolveLifetimeManager(),
new InjectionFactory(
x =>
{
var bootstrapContext = ((ClaimsIdentity)
Thread.CurrentPrincipal.Identity).BootstrapContext as BootstrapContext;
var securityToken = bootstrapContext.SecurityToken;
return new ChannelFactoryWithChannelFactoryOperations<IMyWcfService>("*")
.CreateChannelWithIssuedToken(securityToken);
}));
Or perhaps better yet, create a class that inherits from ChannelFactory and add a method that pulls that combines the above logic into a single CreateChannelWithIssuedTokenUsingBootstrapContext method:
public class ChannelFactoryWithChannelFactoryOperations<T> : ChannelFactory<T>
{
protected ChannelFactoryWithChannelFactoryOperations(Type channelType) : base(channelType)
{
}
public ChannelFactoryWithChannelFactoryOperations()
{
}
public ChannelFactoryWithChannelFactoryOperations(string endpointConfigurationName)
: base(endpointConfigurationName)
{
}
public ChannelFactoryWithChannelFactoryOperations(string endpointConfigurationName,
EndpointAddress remoteAddress) : base(endpointConfigurationName, remoteAddress)
{
}
public ChannelFactoryWithChannelFactoryOperations(Binding binding) : base(binding)
{
}
public ChannelFactoryWithChannelFactoryOperations(Binding binding, string remoteAddress)
: base(binding, remoteAddress)
{
}
public ChannelFactoryWithChannelFactoryOperations(Binding binding, EndpointAddress remoteAddress)
: base(binding, remoteAddress)
{
}
public ChannelFactoryWithChannelFactoryOperations(ServiceEndpoint endpoint) : base(endpoint)
{
}
public T CreateChannelWithIssuedTokenUsingBootstrapContext()
{
var bootstrapContext =
((ClaimsIdentity) Thread.CurrentPrincipal.Identity).BootstrapContext as BootstrapContext;
SecurityToken securityToken = bootstrapContext.SecurityToken;
return CreateChannelWithIssuedToken(securityToken);
}
}
This allows you to then just call this:
container.RegisterType<IMyWcfService>(new PerResolveLifetimeManager(),
new InjectionFactory(
x => new ChannelFactoryWithChannelFactoryOperations<IMyWcfService>("*")
.CreateChannelWithIssuedTokenUsingBootstrapContext()));
Solving issue 3:
Adding upon the complexity of the above two issues, I'm now trying the same thing outside of IIS in my own Self-Hosted WCF service. This same service is completely stateless, so here's where my next dilemma occurs:
Bootstrapping of the security token still occurs, but it isn't occurring on the correct Thread. Unity seems to run its InjectionFactory in a separate Thread to the actual service call execution.
i.e. when the InjectionFactory delegate above executes, the CurrentPrincipal is an unauthorized GenericPrincipal. This is different from what we had in issue 2 above - where it is an authorized ClaimsPrincipal - I believe this is all set up by the IIS session (please do feel free to correct if I'm incorrect).
Funnily enough, if we then replace the above with
container.RegisterType<IMyWcfService>(new PerResolveLifetimeManager(),
new InjectionFactory(
x => new ChannelFactoryWithChannelFactoryOperations<IMyWcfService>("*")
.CreateChannel()));
i.e. now just inject an unsecured Channel object, we can then observe that in our self-hosted WCF service, where we actually try to interact with the channel, the Thread.CurrentPrincipal is the authenticated ClaimsPrincipal with the SecurityToken correctly Bootstrapped on the principal.
So the problem can be summarized as the following:
because the InjectionFactory delegate executes on a thread on which no Authentication/Authorization has yet taken place, the SecurityToken is not actually available to pass to the creation of the channel.
Does anybody have any suggestions on how I might solve this issue? Have I already painted myself into a corner with this particular combination of self-hosted WCF and unity?
Thanks,
Clint
I've found a way to do this, though I'd love a better suggestion. The SessionSecurityTokenHandler ValidateToken method always executes on the same Thread as the InjectionFactory delegate, so we can set that Thread's CurrentPrincipal in the ValidateToken method of a CustomSessionSecurityTokenHandler like so:
public class CustomSessionSecurityTokenHandler: SessionSecurityTokenHandler
{
public override ReadOnlyCollection<ClaimsIdentity> ValidateToken(SecurityToken token)
{
var claimsIdentities = base.ValidateToken(token);
Thread.CurrentPrincipal = new ClaimsPrincipal(claimsIdentities);
return claimsIdentities;
}
}
And then the system.identityModel configuration needs to be modified to include the custom securityTokenHandler:
<securityTokenHandlers>
<remove type="System.IdentityModel.Tokens.SessionSecurityTokenHandler, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"/>
<add type="MyAssembly.CustomSessionSecurityTokenHandler, MyAssembly" />
</securityTokenHandlers>
Having done that, attempts to access the security token from the Bootstrap context then succeed:
container.RegisterType<IControllerConfigurationService>(new PerResolveLifetimeManager(),
new InjectionFactory(
x =>
{
var bootstrapContext = ((ClaimsIdentity)
Thread.CurrentPrincipal.Identity).BootstrapContext as BootstrapContext;
var securityToken = bootstrapContext.SecurityToken;
return new ChannelFactoryWithChannelFactoryOperations<IMyWcfService>("*")
.CreateChannelWithIssuedToken(securityToken);
}));
Please feel free to add any alternative suggestions! :)
Thanks,
Clint
I'm trying to add to a MVC4 webapi project the simple membership provider authentication mechanism found in a MVC 4 web application project, for a hybrid application serving its pages with a rich JS content, which uses AJAX calls to webapi actions to perform its tasks. I need the app users to authenticate before they can work with the apps provided in these pages, so I think I'll be fine with the forms authentication. I thus need to add it to the existing WebApi project and let my authorized-only actions return a 302 (redirect user to login page) rather than a 401.
Anyway, I'm missing something because as soon as I try to use a WebSecurity method I get the following exception:
System.InvalidOperationException was caught
Message=To call this method, the "Membership.Provider" property must be an instance of "ExtendedMembershipProvider".
Source=WebMatrix.WebData
Could anyone suggest a fix? Here are the steps I took for adding authorization:
1) Web.config: add to system.web:
<authentication mode="Forms">
<forms loginUrl="~/Account/Login" timeout="2880" />
</authentication>
Add to appsettings (the 2nd entry is for replacing 401 with 302):
<add key="enableSimpleMembership" value="true"/>
<add key="webapi:EnableSuppressRedirect" value="false" />
Also remove profile, membership and rolemanager sections from the original template (they are not intended to be used with simple membership).
2) add NuGet packages for OpenAuth (DotNetOpenAuth Core, DotNetOpenAuth ext for ASP.NET, DotNetOpenAuth 1.0(a) consumer, DotNetOpenAuth 1.0(a), DotNetOpenAuth OpenID Core, DotNetOpenAuth OpenID Relying Party).
3) add InitializeSimpleMembership.cs to Filters (the code is pretty standard, see below).
4) copy from an MVC web app project the models in AccountModels.cs, all the views in Views/Account, and the AccountController.cs.
The InitializeSimpleMembership code is here:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public sealed class InitializeSimpleMembershipAttribute : ActionFilterAttribute
{
private static SimpleMembershipInitializer _initializer;
private static object _initializerLock = new object();
private static bool _isInitialized;
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
LazyInitializer.EnsureInitialized(ref _initializer, ref _isInitialized, ref _initializerLock);
}
private class SimpleMembershipInitializer
{
private static void SeedData()
{
// seed data: users and roles
if (!WebSecurity.UserExists("TheAdminGuyName"))
WebSecurity.CreateUserAndAccount("TheAdminGuyName", "password");
if (!Roles.RoleExists("administrator")) Roles.CreateRole("administrator");
if (!Roles.IsUserInRole("TheAdminGuyName", "administrator"))
Roles.AddUserToRole("TheAdminGuyName", "administrator");
}
public SimpleMembershipInitializer()
{
Database.SetInitializer<UsersContext>(null);
try
{
using (var context = new UsersContext())
{
if (!context.Database.Exists())
{
((IObjectContextAdapter)context).ObjectContext.CreateDatabase();
}
}
WebSecurity.InitializeDatabaseConnection("DefaultConnection", "User", "UserId", "UserName", autoCreateTables: true);
SeedData();
}
catch (Exception ex)
{
throw new InvalidOperationException("The ASP.NET Simple Membership database could not be initialized. For more information, please see http://go.microsoft.com/fwlink/?LinkId=256588", ex);
}
}
}
}
This might be relevant, as it mentions your error specifically:
http://weblogs.asp.net/jgalloway/archive/2012/08/29/simplemembership-membership-providers-universal-providers-and-the-new-asp-net-4-5-web-forms-and-asp-net-mvc-4-templates.aspx