Using ApiKey and JWT token authentication in middleware .NET 6 application - c#

I wanted to ask for advice for a specific approach using ApiKey and JWT token authentication. My .NET 6 application is like a middleware service, which gets a request from other service through HTTP. After that request, I am checking whether it has JWT token in Headers and if it has, I use it to validate the token. After validating it, I need to pass it to my other service class under the same project, there I create a HttpClient and I want to put that JWT token into it's header section. Unfortunately, I am unable to pass it through. I tried creating 'JwtTokenStore' class, store a JWT there and then pass it with dependency injection. I used AddTransient, but then realized that I got a 'null' value in my service, because it creates another instance of that 'IJwtTokenStore'.
I will give you some code snippets:
ApiKeyHandler:
protected override async Task<AuthenticateResult> HandleAuthenticationAsync()
{
if (Request.Headers.TryGetValue(ApiKeyAuthenticationOptions.ApiKeyHeaderName, out var apiKeyHeaderValues))
{
headerKey = apiKeyHeaderValues.ToArray().FirstOrDefault();
if (!string.IsNullOrEmpty(validKey) && !validKey.Equals(headerKey) && !validKey.Equals(uriKey))
{
return AuthenticateResult.NoResult();
}
}
else
{
// check for JWT token
var jwt = Request.Headers["Authorization"].FirstOrDefault(x => x.StartsWith("Bearer "));
if (string.IsNullOrEmpty(jwt))
return AuthenticateResult.Fail("No ApiKey or JWT token present in request headers.");
// validate JWT token
var tokenHandler = new JwtSecurityTokenHandler();
var validationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
ValidateAudience = false,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey))
};
try
{
var jwtToken = tokenHandler.ReadJwtToken(jwt[7..]);
var expClaim = jwtToken.Claims.FirstOrDefault(x => x.Type == JwtRegisteredClaimNames.Exp)?.Value;
validationParameters.ValidateLifetime = !string.IsNullOrEmpty(expClaim);
tokenHandler.ValidateToken(jwt[7..], validationParameters, out SecurityToken validatedToken);
// Maybe here I should store somewhere my JWT token if it's valid
}
catch
{
return AuthenticateResult.NoResult();
}
}
}
My custom service constructor where I want to add the JWT token if it's valid:
public CustomService(ILogger<CustomService> logger, IConfiguration configuration, IHttpClientFactory httpClientFactory)
{
_logger = logger;
_apiKey = configuration.GetValue<string>("ApiKey");
_httpClient = httpClientFactory?.CreateClient() ?? new HttpClient();
// here I should get that JWT token and check if it's not null, if null
// then I use ApiKey
if (!isApiKey || !string.IsNullOrEmpty(jwt))
{
_httpClient.DefaultRequestHeaders.Add("Authorization", jwt);
}
else //(!string.IsNullOrEmpty(_apiKey))
{
_httpClient.DefaultRequestHeaders.Add("X-Api-Key", _apiKey);
}
}
Can you help me advicing how should I achieve this solution? Don't forget that this middleware application will be getting a lot of requests at the same time, so I need to know which JWT token should I send to my 3rd party service.
All the answers appreciated!
I've tried creating 'JwtTokenStore', put a JWT there and then try to get it in my custom service. After failing (because of AddTransient), I tried creating 'TokenQueue' class with ConcurrentQueue<string> and store the JWT there. But after sending two requests at the same time, in my 3rd party application it only receives the same token.
I saw an answer in this question: Can API Key and JWT Token be used in the same .Net 6 WebAPI but I also stuck at sending that JWT token forward to my custom service.
I also thought about a solution with Dictionary, that I should put token with some user's name or whatsoever, and then getting a token by that. But I am not sure if it's the best solution.

Related

Creating And Validating JWT Tokens In C# .NET

I am using C# asp.net
I want to use jwt token for my webpage.
So whenever the page loads , i have to use jwt , im a beginner so i dont have much idea, i know how it works, but i dont know where to start from or how to implement exactly.
i have a login page and i only need jwt for "online id/admin/username".
Using these
SymmetricSecurityKey
SigningCredentials
JwtHeader
JwtPayload
JwtSecurityToken
JwtSecurityTokenHandler
WriteToken
var token = handler.ReadJwtToken(tokenString);
and googling gives my result for .net core which is not what i want,
can someone help me?
Thankyou
I tried some code snippets but im sure im not doing it the right way
To authenticate using JWT, you must first register the user and store it in your database. When logging in and validating the user with database information, use the following code to create a JWT token.
public static string GenerateJwtToken()
{
DateTime value = DateTime.Now.AddMinutes(20.0);
byte[] bytes = Encoding.ASCII.GetBytes("MIIBrTCCAaGg ...");
SigningCredentials signingCredentials = new SigningCredentials(new SymmetricSecurityKey(bytes), "http://www.w3.org/2001/04/xmldsig-more#hmac-sha256");
SecurityTokenDescriptor tokenDescriptor = new SecurityTokenDescriptor
{
Expires = value,
SigningCredentials = signingCredentials
};
JwtSecurityTokenHandler jwtSecurityTokenHandler = new JwtSecurityTokenHandler();
SecurityToken token = jwtSecurityTokenHandler.CreateToken(tokenDescriptor);
return jwtSecurityTokenHandler.WriteToken(token);
}
Then, in the actions that have the Authorize attribute, you must send the token created above in the request header.
[HttpPost]
[Authorize]
public async Task<IActionResult> Test(TestRequest input)
{
.
.
.
}
I wrote a simple example, you can see the complete implementation example with JWT from this link

JWT token authorization on the c# server side not clear

I have a github project link where the client sends Authorization header to the server containing the JWT token like below. What I can't understand is how on the server side [Authorize(Role.Admin)]can understand the Role.Admin which is application specific enum value (belonging to the account object - details beow).
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// add auth header with jwt if account is logged in and request is to the api url
const account = this.accountService.accountValue;
const isLoggedIn = account && account.jwtToken;
const isApiUrl = request.url.startsWith(environment.apiUrl);
if (isLoggedIn && isApiUrl) {
request = request.clone({
setHeaders: { Authorization: `Bearer ${account.jwtToken}` }
});
}
return next.handle(request);
}
On the server side I have middleware class containing the code that decripts the token like this:
try
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_appSettings.Secret);
tokenHandler.ValidateToken(token, new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false,
// set clockskew to zero so tokens expire exactly at token expiration time (instead of 5 minutes later)
ClockSkew = TimeSpan.Zero
}, out SecurityToken validatedToken);
var jwtToken = (JwtSecurityToken)validatedToken;
var accountId = int.Parse(jwtToken.Claims.First(x => x.Type == "id").Value);
// attach account to context on successful jwt validation
context.Items["Account"] = await dataContext.Accounts.FindAsync(accountId);
}
catch
{
// do nothing if jwt validation fails
// account is not attached to context so request won't have access to secure routes
Console.WriteLine("Failed");
}
So on the server side I have controller that has [Authorize(Role.Admin)] annotation like below which I can't understand - how Authorize can understand the Role:Admin - which is application specific - is it via introspection?
[Authorize(Role.Admin)]
[HttpGet("all-dates")]
public ActionResult<ScheduleDateTimeResponse> GetAllDates()
{
var dates = _accountService.GetAllDates();
return Ok(dates);
}
The project has a custom AuthorizeAttribute for which the Role enumeration is in scope. This differs from the standard attribute from the BCL. This attribute implements IAuthorizationFilter which the asp.net middleware understands must be invoked against this controller action.
Probably the application is referring to some kind of authentication service before the call endpoint. You should be able to see it in the console logs.
Check in solution nugets if there is no some kind of company aut service package
Ofc if you are sure that role is not a part of JWT token.
you can check that with: https://jwt.io/
Sorry...but I cannot add just simply comment yet...

Proper way to test authenticated methods (using bearer tokens) in C# (web api)

I have a Web API with tons of methods that all require a bearer token to be present in order to be used. These methods all extract information from the bearer token.
I want to test whether the API is properly populating the bearer token upon its generation. I'm using the Microsoft.Owin.Testing framework to write my tests. I have a test that looks like this:
[TestMethod]
public async Task test_Login()
{
using (var server = TestServer.Create<Startup>())
{
var req = server.CreateRequest("/authtoken");
req.AddHeader("Content-Type", "application/x-www-form-urlencoded");
req.And(x => x.Content = new StringContent("grant_type=password&username=test&password=1234", System.Text.Encoding.ASCII));
var response = await req.GetAsync();
// Did the request produce a 200 OK response?
Assert.AreEqual(response.StatusCode, System.Net.HttpStatusCode.OK);
// Retrieve the content of the response
string responseBody = await response.Content.ReadAsStringAsync();
// this uses a custom method for deserializing JSON to a dictionary of objects using JSON.NET
Dictionary<string, object> responseData = deserializeToDictionary(responseBody);
// Did the response come with an access token?
Assert.IsTrue(responseData.ContainsKey("access_token"));
}
}
So I'm able to retrieve the string that represents the token. But now I want to actually access that token's contents, and make sure that certain claims were provided.
Code that I would use in an actual authenticated method to check the claims looks like this:
var identity = (ClaimsIdentity)User.Identity;
IEnumerable<Claim> claims = identity.Claims;
var claimTypes = from x in claims select x.Type;
if (!claimTypes.Contains("customData"))
throw new InvalidOperationException("Not authorized");
So what I want to be able to do is, within my test itself, provide the bearer token string and reeceive a User.Identity object or in some other way gain access to the claims that token contains. This is how I want to test whether my method is properly adding the necessary claims to the token.
The "naive" approach could be to write a method in my API that simply returns all the claims in the bearer token it is given. But it feels like this should be unnecessary. ASP.NET is somehow decoding the given token to an object before my controller's method is called. I want to replicate the same action on my own, in my test code.
Can this be done? If so, how?
EDIT: My OWIN startup class instantiates an authentication token provider that I have coded which handles authentication and token generation. In my startup class I have this:
public void Configuration(IAppBuilder app)
{
// Setup configuration object
HttpConfiguration config = new HttpConfiguration();
// Web API configuration and services
// Configure Web API to use only bearer token authentication.
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
// configure the OAUTH server
OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
//AllowInsecureHttp = false,
AllowInsecureHttp = true, // THIS HAS TO BE CHANGED BEFORE PUBLISHING!
TokenEndpointPath = new PathString("/authtoken"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = new API.Middleware.MyOAuthProvider()
};
// Now we setup the actual OWIN pipeline.
// setup CORS support
// in production we will only allow from the correct URLs.
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
// Token Generation
app.UseOAuthAuthorizationServer(OAuthServerOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
// insert actual web API and we're off!
app.UseWebApi(config);
}
Here is the relevant code from my OAuth provider:
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
// Will be used near end of function
bool isValidUser = false;
// Simple sanity check: all usernames must begin with a lowercase character
Match testCheck = Regex.Match(context.UserName, "^[a-z]{1}.+$");
if (testCheck.Success==false)
{
context.SetError("invalid_grant", "Invalid credentials.");
return;
}
string userExtraInfo;
// Here we check the database for a valid user.
// If the user is valid, isValidUser will be set to True.
// Invalid authentications will return null from the method below.
userExtraInfo = DBAccess.getUserInfo(context.UserName, context.Password);
if (userExtraInfo != null) isValidUser = true;
if (!isValidUser)
{
context.SetError("invalid_grant", "Invalid credentials.");
return;
}
// The database validated the user. We will include the username in the token.
string userName = context.UserName;
// generate a claims object
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
// add the username to the token
identity.AddClaim(new Claim(ClaimTypes.Sid, userName));
// add the custom data on the user to the token.
identity.AddClaim(new Claim("customData", userExtraInfo));
// store token expiry so the consumer can determine expiration time
DateTime expiresAt = DateTime.Now.Add(context.Options.AccessTokenExpireTimeSpan);
identity.AddClaim(new Claim("expiry", expiresAt.ToString()));
// Validate the request and generate a token.
context.Validated(identity);
}
The unit test would want to ensure that the customData claim is in fact present in the authentication token. So thus my need for a way to evaluate the token provided to test which claims it contains.
EDIT 2: I've spent some time looking over the Katana source code and searching out some other posts online, and it looks like it's important that I'm hosting this app on IIS, so I would be using SystemWeb. It looks like SystemWeb uses Machine Key encryption for the token. It also looks like the AccessTokenFormat parameter in the options is relevant here.
So now what I'm wondering is if I can instantiate my own "decoder" based on this knowledge. Assuming I will only ever be hosting on IIS, can I instantiate a decoder that can then decode the token and convert it into a Claims object?
The docs on this are kind of sparse and the code seems to throw you all over the place, a lot to try to keep straight in my head.
EDIT 3: I found a project that contains what is supposed to be a bearer token deserializer. I adapted the code in its "API" library and have been trying to use it to decrypt the tokens generated by my API.
I generated a <machineKey...> value using a PowerShell script from Microsoft and placed it both in the Web.config file of the API itself and the App.confg file in the test project.
The tokens still fail to decrypt, however. I receive a Exception thrown: System.Security.Cryptography.CryptographicException with the message "Error occurred during a cryptographic operation." The following is the stacktrace of the error:
at System.Web.Security.Cryptography.HomogenizingCryptoServiceWrapper.HomogenizeErrors(Func`2 func, Byte[] input)
at System.Web.Security.Cryptography.HomogenizingCryptoServiceWrapper.Unprotect(Byte[] protectedData)
at System.Web.Security.MachineKey.Unprotect(ICryptoServiceProvider cryptoServiceProvider, Byte[] protectedData, String[] purposes)
at System.Web.Security.MachineKey.Unprotect(Byte[] protectedData, String[] purposes)
at MyAPI.Tests.BearerTokenAPI.MachineKeyDataProtector.Unprotect(Byte[] protectedData) in D:\Source\MyAPI\MyAPI.WebAPI.Tests\BearerTokenAPI.cs:line 251
at MyAPI.Tests.BearerTokenAPI.SecureDataFormat`1.Unprotect(String protectedText) in D:\Source\MyAPI\MyAPI.WebAPI.Tests\BearerTokenAPI.cs:line 287
At this point I'm stumped. With the MachineKey value set to the same across the entire project, I don't see why I'm unable to decrypt the tokens. I'm guessing the cryptographic error is being deliberately vague, but I am not sure where to start with figuring this out now.
And all I wanted to do was test that the token contains the desired data in a unit test.... :-)
I was finally able to figure out a solution. I added a public variable to my Startup class that exposes the OAuthBearerAuthenticationOptions object passed into the UseBearerTokenAuthentication method. From that object, I'm able to call AccessTokenFormat.Unprotect and get a decrypted token.
I also rewrote my test to instantiate the Startup class separately, so that I have access to the value from within the test.
I still don't understand why the MachineKey thing isn't working, why I'm not able to directly unprotect the token. It would seem that as long as the MachineKey's match, I should be able to decrypt the token, even manually. But at least this seems to work, even if it's not the best solution.
This could probably be done more cleanly, for instance perhaps the Startup class could somehow detect if it's being started under test and pass the object to the test class in some other fashion rather than leaving it hanging out there in the breeze. But for now this seems to do exactly what I needed.
My startup class exposes the variable this way:
public partial class Startup
{
public OAuthBearerAuthenticationOptions oabao;
public void Configuration(IAppBuilder app)
{
// repeated code omitted
// Token Generation
app.UseOAuthAuthorizationServer(OAuthServerOptions);
oabao = new OAuthBearerAuthenticationOptions();
app.UseOAuthBearerAuthentication(oabao);
// insert actual web API and we're off!
app.UseWebApi(config);
}
}
My test now looks like this:
[TestMethod]
public async Task Test_SignIn()
{
Startup owinStartup = new Startup();
Action<IAppBuilder> owinStartupAction = new Action<IAppBuilder>(owinStartup.Configuration);
using (var server = TestServer.Create(owinStartupAction))
{
var req = server.CreateRequest("/authtoken");
req.AddHeader("Content-Type", "application/x-www-form-urlencoded");
// repeated code omitted
// Is the access token of an appropriate length?
string access_token = responseData["access_token"].ToString();
Assert.IsTrue(access_token.Length > 32);
AuthenticationTicket token = owinStartup.oabao.AccessTokenFormat.Unprotect(access_token);
// now I can check whatever I want on the token.
}
}
Hopefully all my efforts will help someone else trying to do something similar.

AngularJs, WebAPI, JWT, with (integrated) Windows authentication

I've asked a question before and the answer that was given was correct but the farther I go down this rabbit hole the more I realize; I don't think I was asking the right question.
Let me just explain this in the most simple terms I can... I have a AngularJS single page app (client), that points at an asp.net webapi (OWIN) site (Resource server?), and a separate asp.net "authorization/authentiation" server.
The auth server will provide authentication and authorization for multiple applications. I need to be able to use the Authorize attribute in the resource server, as well as get a token from from angular. I also need to use windows authentication (integrated) for everything, no usernames or passwords. The claims information is stored in a database and needs to be added to the token.
I've done a SSO style claims authoriztion implementation in asp.net core using openiddict with JwtBearerToken and 'password flow?' And wanted to try to do something similar (token, etc). I have a basic understanding of how that works from my previous implmentation, but I am completely lost trying to figure out how to get JWT working with Windows Auth. The answer to my previous question provided some good suggestions but I am having a hard time seeing how that applies in this scenario.
Currently I have been trying to get IdentityServer3 to do this, using the WindowsAuthentication extensions, mainly pulled from the samples. But I am really struggling to tie this together with the client and actually get something working. The current client and server code is below, mind you I really don't know if this is even close to the correct solution.
Client:
app.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Passive,
AuthenticationType = "windows",
Authority = "http://localhost:21989",
ClientId = "mvc.owin.implicit",
ClientSecret = "api-secret",
RequiredScopes = new[] { "api" }
});
AuthServer:
app.Map("/windows", ConfigureWindowsTokenProvider);
app.Use(async (context, next) =>
{
if (context.Request.Uri.AbsolutePath.EndsWith("/token", StringComparison.OrdinalIgnoreCase))
{
if (context.Authentication.User == null ||
!context.Authentication.User.Identity.IsAuthenticated)
{
context.Response.StatusCode = 401;
return;
}
}
await next();
});
var factory = new IdentityServerServiceFactory()
.UseInMemoryClients(Clients.Get())
.UseInMemoryScopes(Scopes.Get());
var options = new IdentityServerOptions
{
SigningCertificate = Certificate.Load(),
Factory = factory,
AuthenticationOptions = new AuthenticationOptions
{
EnableLocalLogin = false,
IdentityProviders = ConfigureIdentityProviders
},
RequireSsl = false
};
app.UseIdentityServer(options);
private static void ConfigureWindowsTokenProvider(IAppBuilder app)
{
app.UseWindowsAuthenticationService(new WindowsAuthenticationOptions
{
IdpReplyUrl = "http://localhost:21989",
SigningCertificate = Certificate.Load(),
EnableOAuth2Endpoint = false
});
}
private void ConfigureIdentityProviders(IAppBuilder app, string signInAsType)
{
var wsFederation = new WsFederationAuthenticationOptions
{
AuthenticationType = "windows",
Caption = "Windows",
SignInAsAuthenticationType = signInAsType,
MetadataAddress = "http://localhost:21989",
Wtrealm = "urn:idsrv3"
};
app.UseWsFederationAuthentication(wsFederation);
}
EDIT: I see the auth endpoints requests for "/.well-known/openid-configuration" as well as "/.well-known/jwks" and I have the Authorize attribute on a controller action which is being called, but I dont see anything else happening on the auth side. I also added a ICustomClaimsProvider implmentation to the usewindowsauthservice WindowsAuthenticationOptions but that doesnt even get called.
I've done a SSO style claims authoriztion implementation in asp.net core using openiddict with JwtBearerToken and 'password flow?'
If you were to use OpenIddict with Windows authentication, it would be quite easy to implement using the OAuth2/OpenID Connect implicit flow (which is the most appropriate flow for a JS app), without needing any WS-Federation proxy:
Startup configuration:
public void ConfigureServices(IServiceCollection services)
{
// Register the OpenIddict services.
services.AddOpenIddict(options =>
{
// Register the Entity Framework stores.
options.AddEntityFrameworkCoreStores<ApplicationDbContext>();
// Register the ASP.NET Core MVC binder used by OpenIddict.
// Note: if you don't call this method, you won't be able to
// bind OpenIdConnectRequest or OpenIdConnectResponse parameters.
options.AddMvcBinders();
// Enable the authorization endpoint.
options.EnableAuthorizationEndpoint("/connect/authorize");
// Enable the implicit flow.
options.AllowImplicitFlow();
// During development, you can disable the HTTPS requirement.
options.DisableHttpsRequirement();
// Register a new ephemeral key, that is discarded when the application
// shuts down. Tokens signed using this key are automatically invalidated.
// This method should only be used during development.
options.AddEphemeralSigningKey();
});
// Note: when using WebListener instead of IIS/Kestrel, the following lines must be uncommented:
//
// services.Configure<WebListenerOptions>(options =>
// {
// options.ListenerSettings.Authentication.AllowAnonymous = true;
// options.ListenerSettings.Authentication.Schemes = AuthenticationSchemes.Negotiate;
// });
}
Authorization controller:
public class AuthorizationController : Controller
{
// Warning: extreme caution must be taken to ensure the authorization endpoint is not included in a CORS policy
// that would allow an attacker to force a victim to silently authenticate with his Windows credentials
// and retrieve an access token using a cross-domain AJAX call. Avoiding CORS is strongly recommended.
[HttpGet("~/connect/authorize")]
public async Task<IActionResult> Authorize(OpenIdConnectRequest request)
{
// Retrieve the Windows principal: if a null value is returned, apply an HTTP challenge
// to allow IIS/WebListener to initiate the unmanaged integrated authentication dance.
var principal = await HttpContext.Authentication.AuthenticateAsync(IISDefaults.Negotiate);
if (principal == null)
{
return Challenge(IISDefaults.Negotiate);
}
// Note: while the principal is always a WindowsPrincipal object when using Kestrel behind IIS,
// a WindowsPrincipal instance must be manually created from the WindowsIdentity with WebListener.
var ticket = CreateTicket(request, principal as WindowsPrincipal ?? new WindowsPrincipal((WindowsIdentity) principal.Identity));
// Immediately return an authorization response without displaying a consent screen.
return SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme);
}
private AuthenticationTicket CreateTicket(OpenIdConnectRequest request, WindowsPrincipal principal)
{
// Create a new ClaimsIdentity containing the claims that
// will be used to create an id_token, a token or a code.
var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme);
// Note: the JWT/OIDC "sub" claim is required by OpenIddict
// but is not automatically added to the Windows principal, so
// the primary security identifier is used as a fallback value.
identity.AddClaim(OpenIdConnectConstants.Claims.Subject, principal.GetClaim(ClaimTypes.PrimarySid));
// Note: by default, claims are NOT automatically included in the access and identity tokens.
// To allow OpenIddict to serialize them, you must attach them a destination, that specifies
// whether they should be included in access tokens, in identity tokens or in both.
foreach (var claim in principal.Claims)
{
// In this sample, every claim is serialized in both the access and the identity tokens.
// In a real world application, you'd probably want to exclude confidential claims
// or apply a claims policy based on the scopes requested by the client application.
claim.SetDestinations(OpenIdConnectConstants.Destinations.AccessToken,
OpenIdConnectConstants.Destinations.IdentityToken);
// Copy the claim from the Windows principal to the new identity.
identity.AddClaim(claim);
}
// Create a new authentication ticket holding the user identity.
return new AuthenticationTicket(
new ClaimsPrincipal(identity),
new AuthenticationProperties(),
OpenIdConnectServerDefaults.AuthenticationScheme);
}
}
A similar scenario can be implemented in legacy ASP.NET apps using the OWIN/Katana version of ASOS, the OpenID Connect server middleware behind OpenIddict:
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.UseOpenIdConnectServer(options =>
{
// Register a new ephemeral key, that is discarded when the application
// shuts down. Tokens signed using this key are automatically invalidated.
// This method should only be used during development.
options.SigningCredentials.AddEphemeralKey();
// Enable the authorization endpoint.
options.AuthorizationEndpointPath = new PathString("/connect/authorize");
// During development, you can disable the HTTPS requirement.
options.AllowInsecureHttp = true;
// Implement the ValidateAuthorizationRequest event to validate the response_type,
// the client_id and the redirect_uri provided by the client application.
options.Provider.OnValidateAuthorizationRequest = context =>
{
if (!context.Request.IsImplicitFlow())
{
context.Reject(
error: OpenIdConnectConstants.Errors.UnsupportedResponseType,
description: "The provided response_type is invalid.");
return Task.FromResult(0);
}
if (!string.Equals(context.ClientId, "spa-application", StringComparison.Ordinal))
{
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidClient,
description: "The provided client_id is invalid.");
return Task.FromResult(0);
}
if (!string.Equals(context.RedirectUri, "http://spa-app.com/redirect_uri", StringComparison.Ordinal))
{
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidClient,
description: "The provided redirect_uri is invalid.");
return Task.FromResult(0);
}
context.Validate();
return Task.FromResult(0);
};
// Implement the HandleAuthorizationRequest event to return an implicit authorization response.
options.Provider.OnHandleAuthorizationRequest = context =>
{
// Retrieve the Windows principal: if a null value is returned, apply an HTTP challenge
// to allow IIS/SystemWeb to initiate the unmanaged integrated authentication dance.
var principal = context.OwinContext.Authentication.User as WindowsPrincipal;
if (principal == null)
{
context.OwinContext.Authentication.Challenge();
return Task.FromResult(0);
}
// Create a new ClaimsIdentity containing the claims that
// will be used to create an id_token, a token or a code.
var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationType);
// Note: the JWT/OIDC "sub" claim is required by OpenIddict
// but is not automatically added to the Windows principal, so
// the primary security identifier is used as a fallback value.
identity.AddClaim(OpenIdConnectConstants.Claims.Subject, principal.GetClaim(ClaimTypes.PrimarySid));
// Note: by default, claims are NOT automatically included in the access and identity tokens.
// To allow OpenIddict to serialize them, you must attach them a destination, that specifies
// whether they should be included in access tokens, in identity tokens or in both.
foreach (var claim in principal.Claims)
{
// In this sample, every claim is serialized in both the access and the identity tokens.
// In a real world application, you'd probably want to exclude confidential claims
// or apply a claims policy based on the scopes requested by the client application.
claim.SetDestinations(OpenIdConnectConstants.Destinations.AccessToken,
OpenIdConnectConstants.Destinations.IdentityToken);
// Copy the claim from the Windows principal to the new identity.
identity.AddClaim(claim);
}
context.Validate(identity);
return Task.FromResult(0);
};
});
}
}
The client-side code shouldn't be different from any other JS application using the implicit flow. You can take a look at this sample to see how you can implement it with the oidc-client JS library: https://github.com/openiddict/openiddict-samples/tree/master/samples/ImplicitFlow/AureliaApp
So ultimately the whole point here was to augment claims on the existing ClaimsPrincipal with claims from the database and hopefully be able to use JWT's in the javascript. I was unable to get that to work using IdentityServer3. I ended up rolling my own rudimentary solution by implementing IAuthenticationFilter and IAuthorizationFilter using an attribute on the actions to supply the claim name.
First the authorize attribute does nothing but take the name of the claim that the user should have to access the action.
public class AuthorizeClaimAttribute : Attribute
{
public string ClaimValue;
public AuthorizeClaimAttribute(string value)
{
ClaimValue = value;
}
}
Then the Authorize filter which does nothing but check to see if the user has the claim from the attribute.
public class AuthorizeClaimFilter : AuthorizeAttribute, IAuthorizationFilter
{
private readonly string _claimValue;
public AuthorizeClaimFilter(string claimValue)
{
_claimValue = claimValue;
}
public override async Task OnAuthorizationAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
{
var p = actionContext.RequestContext.Principal as ClaimsPrincipal;
if(!p.HasClaim("process", _claimValue))
HandleUnauthorizedRequest(actionContext);
await Task.FromResult(0);
}
protected override void HandleUnauthorizedRequest(HttpActionContext actionContext)
{
actionContext.Response = new HttpResponseMessage(HttpStatusCode.Forbidden);
}
}
The Authentication filter which calls the webapi endpoint (which is using windows authentication) to get the users list of custom "claims" from the database. The WebAPI is just a standard webapi instance, nothing special at all.
public class ClaimAuthenticationFilter : ActionFilterAttribute, IAuthenticationFilter
{
public ClaimAuthenticationFilter()
{
}
public async Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
{
if (context.Principal != null && context.Principal.Identity.IsAuthenticated)
{
var windowsPrincipal = context.Principal as WindowsPrincipal;
var handler = new HttpClientHandler()
{
UseDefaultCredentials = true
};
HttpClient client = new HttpClient(handler);
client.BaseAddress = new Uri("http://localhost:21989");// to be stored in config
var response = await client.GetAsync("/Security");
var contents = await response.Content.ReadAsStringAsync();
var claimsmodel = JsonConvert.DeserializeObject<List<ClaimsModel>>(contents);
if (windowsPrincipal != null)
{
var name = windowsPrincipal.Identity.Name;
var identity = new ClaimsIdentity();
foreach (var claim in claimsmodel)
{
identity.AddClaim(new Claim("process", claim.ClaimName));
}
var claimsPrincipal = new ClaimsPrincipal(identity);
context.Principal = claimsPrincipal;
}
}
await Task.FromResult(0);
}
public async Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken)
{
var challenge = new AuthenticationHeaderValue("Negotiate");
context.Result = new ResultWithChallenge(challenge, context.Result);
await Task.FromResult(0);
}
}
The filters are bound to the attribute using my DI framework (ninject in this case).
this.BindHttpFilter<AuthorizeClaimFilter>(FilterScope.Action)
.WhenActionMethodHas<AuthorizeClaimAttribute>()
.WithConstructorArgumentFromActionAttribute<AuthorizeClaimAttribute>("claimValue", o => o.ClaimValue);
This works for my purposes, and the web api endpoint consumable both in the WebAPI instance and in the AngularJS app. However it is obviously NOT ideal. I really would have preferred to use 'real' authentication/authorization processes. I hesitate to say this is the answer to the question, but it is the only solution I could come up with the time that I had to make something work.

Add cookies to Katana Hosted WebAPI with Basic Authentication

I have implemented a Basic Authentication Middleware for Katana (Code below).
(My client is hosted on a cross domain then the actually API).
The browser can skip the preflight request if the following conditions
are true:
The request method is GET, HEAD, or POST, and The application does not
set any request headers other than Accept, Accept-Language,
Content-Language, Content-Type, or Last-Event-ID, and The Content-Type
header (if set) is one of the following:
application/x-www-form-urlencoded multipart/form-data text/plain
In javascript I set the authentication header( with jquery, beforeSend) on all requests for the server to accept the requests. This means that above will send the Options request on all requests. I dont want that.
function make_base_auth(user, password) {
var tok = user + ':' + password;
var hash = Base64.encode(tok);
return "Basic " + hash;
}
What would I do to get around this? My idea would be to have the user information stored in a cookie when he has been authenticated.
I also saw in the katana project that are a Microsoft.Owin.Security.Cookies - is this maybe what i want instead of my own basic authentication?
BasicAuthenticationMiddleware.cs
using Microsoft.Owin;
using Microsoft.Owin.Logging;
using Microsoft.Owin.Security.Infrastructure;
using Owin;
namespace Composite.WindowsAzure.Management.Owin
{
public class BasicAuthenticationMiddleware : AuthenticationMiddleware<BasicAuthenticationOptions>
{
private readonly ILogger _logger;
public BasicAuthenticationMiddleware(
OwinMiddleware next,
IAppBuilder app,
BasicAuthenticationOptions options)
: base(next, options)
{
_logger = app.CreateLogger<BasicAuthenticationMiddleware>();
}
protected override AuthenticationHandler<BasicAuthenticationOptions> CreateHandler()
{
return new BasicAuthenticationHandler(_logger);
}
}
}
BasicAuthenticationHandler.cs
using Microsoft.Owin.Logging;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Infrastructure;
using System;
using System.Text;
using System.Threading.Tasks;
namespace Composite.WindowsAzure.Management.Owin
{
public class BasicAuthenticationHandler : AuthenticationHandler<BasicAuthenticationOptions>
{
private readonly ILogger _logger;
public BasicAuthenticationHandler(ILogger logger)
{
_logger = logger;
}
protected override Task ApplyResponseChallengeAsync()
{
_logger.WriteVerbose("ApplyResponseChallenge");
if (Response.StatusCode != 401)
{
return Task.FromResult<object>(null);
}
AuthenticationResponseChallenge challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode);
if (challenge != null)
{
Response.Headers.Set("WWW-Authenticate", "Basic");
}
return Task.FromResult<object>(null);
}
protected override async Task<AuthenticationTicket> AuthenticateCoreAsync()
{
_logger.WriteVerbose("AuthenticateCore");
AuthenticationProperties properties = null;
var header = Request.Headers["Authorization"];
if (!String.IsNullOrWhiteSpace(header))
{
var authHeader = System.Net.Http.Headers.AuthenticationHeaderValue.Parse(header);
if ("Basic".Equals(authHeader.Scheme, StringComparison.OrdinalIgnoreCase))
{
string parameter = Encoding.UTF8.GetString(Convert.FromBase64String(authHeader.Parameter));
var parts = parameter.Split(':');
if (parts.Length != 2)
return null;
var identity = await Options.Provider.AuthenticateAsync(userName: parts[0], password: parts[1], cancellationToken: Request.CallCancelled);
return new AuthenticationTicket(identity, properties);
}
}
return null;
}
}
}
Options.Provider.AuthenticateAsync validated the username/password and return the identity if authenticated.
Specifications
What I am trying to solve is: I have a Owin Hosted WebAPI deployed with N Azure Cloud Services. Each of them are linked to a storage account that holds a list of username/hashed passwords.
From my client I am adding any of these N services to the client and can then communicate with them by their webapis. They are locked down with authentication. The first step is to validate the users over basic authentication scheme with the list provided above. After that, I hope its easy to add other authentication schemes very easy as of the Owin, UseWindowsAzureAuthentication ect, or UseFacebookAuthentication. (I do have a challenge here, as the webapi do not have web frontend other then the cross domain site that adds the services).
If your good at Katana and want to work alittle with me on this, feel free to drop me a mail at pks#s-innovations.net. I will provide the answer here at the end also.
Update
Based on answer I have done the following:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Application",
AuthenticationMode = AuthenticationMode.Active,
LoginPath = "/Login",
LogoutPath = "/Logout",
Provider = new CookieAuthenticationProvider
{
OnValidateIdentity = context =>
{
// context.RejectIdentity();
return Task.FromResult<object>(null);
},
OnResponseSignIn = context =>
{
}
}
});
app.SetDefaultSignInAsAuthenticationType("Application");
I assume that it has to be in AuthenticationMode = Active, else the Authorize attributes wont work?
What exactly needs to be in my webapi controller to do the exchange for a cookie?
public async Task<HttpResponseMessage> Get()
{
var context = Request.GetOwinContext();
//Validate Username and password
context.Authentication.SignIn(new AuthenticationProperties()
{
IsPersistent = true
},
new ClaimsIdentity(new[] { new Claim(ClaimsIdentity.DefaultNameClaimType, "MyUserName") }, "Application"));
return Request.CreateResponse(HttpStatusCode.OK);
}
Is above okay?
Current Solution
I have added my BasicAuthenticationMiddleware as the active one, added the above CookieMiddleware as passive.
Then in the AuthenticateCoreAsync i do a check if I can login with the Cookie,
var authContext = await Context.Authentication.AuthenticateAsync("Application");
if (authContext != null)
return new AuthenticationTicket(authContext.Identity, authContext.Properties);
So I can now exchange from webapi controller a username/pass to a cookie and i can also use the Basic Scheme directly for a setup that dont use cookies.
If web api and javascript file are from different origins and you have to add authorization header or cookie header to the request, you cannot prevent browser from sending preflight request. Otherwise it will cause CSRF attack to any protected web api.
You can use OWIN Cors package or Web API Cors package to enable CORS scenario, which can handle the preflight request for you.
OWIN cookie middleware is responsible for setting auth cookie and verify it. It seems to be what you want.
BTW, Basic auth challenge can cause browser to pop up browser auth dialog, which is not expected in most of the web application. Not sure if it's what you want. Instead, using form post to send user name and password and exchange them with cookie is what common web app does.
If you have VS 2013 RC or VWD 2013 RC installed on your machine, you can create an MVC project with Individual auth enabled. The template uses cookie middleware and form post login. Although it's MVC controller, you can simply convert the code to Web API.
[Update]
Regarding preflight request, it will be sent even with cookie header according to the spec. You may consider to add Max Age header to make it be cached on the browser.
JSONP is another option which doesn't require preflight.
[Update2] In order to set cookie by owin middleware, please use the following sample code.
var identity = new ClaimsIdentity(CookieAuthenticationDefaults.ApplicationAuthenticationType);
identity.AddClaim(new Claim(ClaimTypes.Name, "Test"));
AuthenticationManager.AuthenticationResponseGrant = new AuthenticationResponseGrant(identity, new AuthenticationProperties()
{
IsPersistent = true
});

Categories

Resources