Trying to configure my Web Api as Resource Server. My client logs into Auth0 and gets Bearer token, so Authorization Server is Auth0 not my Api. Then they send request along with the Bearer token to my Api. In my ASP.Net Web Api I have implemented following OWIN configuration in Startup class to validate the request JWT Bearer token issued by Auth0 as instructed here.
Statup:
public class Startup
{
public void Configuration(IAppBuilder app)
{
var auth0Options = new Auth0Options()
{
Issuer = $"https://{ConfigurationManager.AppSettings["Auth0ApiInternalDomain"]}/",
Audience = ConfigurationManager.AppSettings["Auth0ApiInternalAudience"],
ClientId = ConfigurationManager.AppSettings["Auth0ApiInternalClientID"]
};
Auth0Config.Configure(app, auth0Options);
// Configure Web API
WebApiConfig.Configure(app);
}
}
and Auth0Config class:
public class Auth0Config
{
public static void Configure(IAppBuilder app, Auth0Options options)
{
if (options == null)
throw new ArgumentNullException(nameof(options));
var keyResolver = new OpenIdConnectSigningKeyResolver(options.Issuer);
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
TokenValidationParameters = new TokenValidationParameters()
{
ValidAudience = options.Audience,
ValidIssuer = options.Issuer,
IssuerSigningKeyResolver = (token, securityToken, identifier, parameters) => keyResolver.GetSigningKey(identifier),
ValidateLifetime = true,
ValidateIssuer = true,
ValidateAudience = true,
LifetimeValidator = (DateTime? notBefore, DateTime? expires, SecurityToken securityToken, TokenValidationParameters validationParameters) =>
{
if (expires.Value < DateTime.UtcNow)
{
return false;
}
return true;
}
}
});
}
}
I pass Audience, Issuer and CliedntId from my app.config to this method. My intention is to figure out whether the Bearer token coming from the client to my Api is valid or not (here as first step I need to validate expiration date). When I debug my code for the incoming request, LifetimeValidator works fine and returns false for the expired token. I decorated my action with [Authorize] and expected to get 401 error but the actual response is 200 and it seems it ignores the LifetimeValidator implementation.
My action:
[Authorize]
public IHttpActionResult Get(string id)
{
var result = _bookingService.GetBooking(id);
if (result == null) return NotFound();
return Ok(result);
}
Am I missing something to get it right?
Is this a good approach to validate token expiration?
Is it possible to use OWIN only to validate the request Bearer token that has been issued out of web api application?
It turned out Invoke method of OwinMiddleware class had been overridden in my application to find Username from token and inject it to Request.User. Not sure why but somehow it ignores OWIN token validation functionality and didn't check Audience, Issuer or Expiration time.
public static void Configure(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
app.Use<HttpUsernameInjector>();
app.UseWebApi(config);
}
public class HttpUsernameInjector : OwinMiddleware
{
public HttpUsernameInjector(OwinMiddleware next)
: base(next){}
public override async Task Invoke(IOwinContext context)
{
const string usernameClaimKey = "myUserNameClaimKey";
var bearerString = context.Request.Headers["Authorization"];
if (bearerString != null && bearerString.StartsWith("Bearer ", StringComparison.InvariantCultureIgnoreCase))
{
var tokenString = bearerString.Substring(7);
var token = new JwtSecurityToken(tokenString);
var claims = token.Claims.ToList();
var username = claims.FirstOrDefault(x => x.Type == usernameClaimKey);
if (username == null) throw new Exception("Token should have username");
// Add to HttpContext
var genericPrincipal = new GenericPrincipal(new GenericIdentity(username.Value), new string[] { });
IPrincipal principal = genericPrincipal;
context.Request.User = principal;
}
await Next.Invoke(context);
}
}
by removing this class, OWIN token validation works fine!
Base on my research, the best token validation approaches in Web Api are OWIN and also IAuthenticationFilter.
It is possible as Resource Server and Authorization Server are decoupled. More info can be found here
Update
Found the solution here to stop OwinMiddleware suppressing my token validation logic
Related
i have a strange problem with Servicestack Authentication.
I've developed an Asp .Net Core web app (.net core 3.1) in which is implemented a servicestack authentication with credentials auth provider. Everything work correctly if i authenticate with any browsers.
Instead if i try to authenticate from external application with JsonServiceClient pointing to servicestack /auth/{provider} api i've this problem:
authentication goes well but the JsonServiceClient object stores a SessionId in cookies (s-id/s-pid) different from the SessionId of AuthenticateResponse. Here my example.
Authenticate request = new Authenticate()
{
provider = "credentials",
UserName = username,
Password = password,
RememberMe = true
};
var client = new JsonServiceClient(webappUrl);
AuthenticateResponse response = await client.PostAsync(request);
var cookies = client.GetCookieValues();
If i check values in cookies variable i see that there are s-id and s-pid completely different from the sessionId of the response.
The other strange thing is that if i repeat the authentication a second time under those lines of code, now the s-pid cookie is equal to sessionId of response!
Why??
In the startup of web app i have these lines of code:
public new void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options => options.EnableEndpointRouting = false);
// Per accedere all'httpcontext della request
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
// Per accedere alla request context della request
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
// Registro il json di configurazione (innietta l'appSettings)
services.AddSingleton(Configuration);
// Filters
services.AddSingleton<ModulePermissionFilter>();
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => false;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme);
... other lines of code
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IBackgroundJobClient backgroundJobs)
{
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseAuthentication();
app.UseServiceStack(new AppHost
{
AppSettings = new NetCoreAppSettings(Configuration)
});
}
public class AppHost : AppHostBase
{
public AppHost() : base("webapp", typeof(BaseServices).Assembly) { }
// Configure your AppHost with the necessary configuration and dependencies your App needs
public override void Configure(Container container)
{
SetConfig(new HostConfig
{
UseCamelCase = false,
WriteErrorsToResponse = true,
ReturnsInnerException = true,
AllowNonHttpOnlyCookies = false,
DebugMode = AppSettings.Get(nameof(HostConfig.DebugMode), HostingEnvironment.IsDevelopment()),
// Restrict cookies to domain level in order to support PflowV2
RestrictAllCookiesToDomain = !string.IsNullOrEmpty(AppSettings.Get("RestrictAllCookiesToDomain", "")) && AppSettings.Get("RestrictAllCookiesToDomain", "").ToLower() != "localhost" ? AppSettings.Get("RestrictAllCookiesToDomain", "") : null
});
// Create DBFactory for cache
var defaultConnection = appHost.AppSettings.Get<string>("ConnectionStrings:Webapp");
var dbFactory = new OrmLiteConnectionFactory(defaultConnection, SqlServerDialect.Provider);
// Register ormlite sql session and cache
appHost.Register<IDbConnectionFactory>(dbFactory);
appHost.RegisterAs<OrmLiteCacheClient, ICacheClient>();
appHost.Resolve<ICacheClient>().InitSchema();
appHost.Register<ISessionFactory>(new SessionFactory(appHost.Resolve<ICacheClient>()));
//Tell ServiceStack you want to persist User Auth Info in SQL Server
appHost.Register<IAuthRepository>(new OrmLiteAuthRepository(dbFactory));
appHost.Resolve<IAuthRepository>().InitSchema();
var sessionMinute = appHost.AppSettings.Get("SessionTimeoutMinute", 15);
// Adding custom usersession and custom auth provider
Plugins.Add(new AuthFeature(() => new CustomUserSession(), new IAuthProvider[] { new CustomCredentialsAuthProvider(), new ApiKeyAuthProvider() })
{
HtmlRedirect = "/Account/Login", // Redirect to login if session is expired
IncludeAssignRoleServices = false,
SessionExpiry = TimeSpan.FromHours(sessionMinute),
});
Plugins.Add(new SessionFeature());
}
}
I am using ASP.NetCore 2.2 (probably moving to 3.0 soon). I have an Azure App Service application.
I want to have an API that clients will use an API token (client secret) to authenticate with so that they can run without requiring interactive authorization.
The UI portion will require Azure Active Directory authentication.
How do I wire this up these two different auth methods my ASP.Net Core app?
How-to
Firstly, we need an AuthenticationHandler & Options to authenticate request with an API Token(Client Secret). Suppose you've created such an Authentication Handler & Options:
public class ClientSecretAuthenOpts : AuthenticationSchemeOptions
{
public const string DefaultAuthenticationSchemeName = "ClientSecret";
}
public class ClientSecretAuthenticationHandler : AuthenticationHandler<ClientSecretAuthenOpts>
{
public ClientSecretAuthenticationHandler(IOptionsMonitor<ClientSecretAuthenOpts> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
: base(options, logger, encoder, clock) { }
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
// ... authenticate request
}
}
And then register multiple Authentication Schemes one by one:
// add mulitple authentication schemes
services.AddAuthentication(AzureADDefaults.AuthenticationScheme) // set AzureAD as the default for users (using the UI)
.AddAzureAD(options => Configuration.Bind("AzureAD", options)) // setup AzureAD Authentication
.AddScheme<ClientSecretAuthenOpts,ClientSecretAuthenticationHandler>( // setup ClientSecret Authentication
ClientSecretAuthenOpts.DefaultAuthenticationSchemeName,
opts=>{ }
);
// post configuration for OIDC
services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options =>{
options.Authority = options.Authority + "/v2.0/"; // Microsoft identity platform
options.TokenValidationParameters.ValidateIssuer = false; // accept several tenants
});
Finally, in order to enable multiple authentication schemes at the same time, we need override the default policy:
services.AddAuthorization(opts => {
// allow AzureAD & our own ClientSecret Authentication at the same time
var pb = new AuthorizationPolicyBuilder(
ClientSecretAuthenOpts.DefaultAuthenticationSchemeName,
"AzureAD"
);
opts.DefaultPolicy = pb.RequireAuthenticatedUser().Build();
});
Demo & Test
Suppose your API token (client secret) is sent in Request Header as below :
GET https://localhost:5001/Home/Privacy HTTP/1.1
Api-Subscription-Id: Smith
Api-Subscription-Key: top secret
To avoid hardcoding the header name, I create add two properties in the options:
public class ClientSecretAuthenOpts : AuthenticationSchemeOptions
{
public const string DefaultAuthenticationSchemeName = "ClientSecret";
public string ApiClientIdHeadername {get;set;}= "Api-Subscription-Id";
public string ApiClientTokenHeaderName {get;set;}= "Api-Subscription-Key";
}
In order to authenticate above request, I create a custom Authentication Handler as below:
public class ClientSecretAuthenticationHandler : AuthenticationHandler<ClientSecretAuthenOpts>
{
public ClientSecretAuthenticationHandler(IOptionsMonitor<ClientSecretAuthenOpts> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
: base(options, logger, encoder, clock) { }
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
// if there's no header for Client ID & Client Sercet, skip
if(
Context.Request.Headers.TryGetValue(Options.ApiClientIdHeadername, out var clientIdHeader) &&
Context.Request.Headers.TryGetValue(Options.ApiClientTokenHeaderName, out var clientSecretHeader)
){
// validate client's id & secret
var clientId = clientIdHeader.FirstOrDefault();
var clientKey = clientSecretHeader.FirstOrDefault();
var (valid, id) = await ValidateApiKeyAsync(clientId, clientKey);
if(!valid){
return AuthenticateResult.Fail($"invalid token:{clientKey}");
}else{
var principal = new ClaimsPrincipal(id);
var ticket = new AuthenticationTicket(principal, new AuthenticationProperties(), this.Scheme.Name);
return AuthenticateResult.Success(ticket);
}
}
return AuthenticateResult.NoResult();
}
private Task<(bool, ClaimsIdentity)> ValidateApiKeyAsync(string clientId,string clientSecret)
{
ClaimsIdentity id = null;
// fake: need check key against the Database or other service
if(clientId=="Smith" && clientSecret == "top secret"){
id = new ClaimsIdentity(
new Claim[]{
new Claim(ClaimTypes.NameIdentifier, "client id from db or from the request"),
new Claim("Add Any Claim", "add the value as you like"),
// ...
}
,this.Scheme.Name
);
return Task.FromResult((true, id));
}
return Task.FromResult((false,id));
}
}
Test
Let's say we have a controller Action annotated with the [Authorize] attribute
[Authorize]
public IActionResult Privacy()
{
return Ok("hello,world");
}
When accessing the url within a browser (UI, without the header), the user will be redirected to Azure AD Authentication if he's not signed in.
When testing the above request with a client secret,
And we'll get the "hello,world" response:
We have a .Net Core 2.0 Web API project. I have added the hangfire there. We don't have any web page in the project and I use JWT for authorization. So I'm not able to do the authorization for hangfire using the Authorize(DashboardContext context). Is there any way we can pass some sort of API key on the url to authorize the user for dashboard?
Thanks
Yes, you can do that by using cookies, I will explain the idea to you with some code...
Firstly once the user login (you generate the token) you must store the token you generated to cookies in the browser and then when you want to access to Hangfire dashboard you must read the token from cookies and then check the roles...
the code to store the token in cookies:
`
httpContext.Response.Cookies.Append("token", userToken.AccessToken,
new Microsoft.AspNetCore.Http.CookieOptions { Expires = DateTime.Now.AddMinutes(6000) });
`
make sure you enabled cookies by:
`
AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddCookie()
.AddJwtBearer(cfg => your configs);
`
then the authorize method will be like this:
`
public bool Authorize(DashboardContext context)
{
var httpContext = context.GetHttpContext();
var jwtToken = string.Empty;
if (httpContext.Request.Cookies.ContainsKey("token"))
{
httpContext.Request.Cookies.TryGetValue("token", out jwtToken);
}
else
return false;
if (string.IsNullOrEmpty(jwtToken))
{
return false;
}
var handler = new JwtSecurityTokenHandler();
try
{
var claim = _tokenService.GetClaimsPrincipal(jwtToken);
return claim != null && claim.IsInRole(RolesConstants.ADMIN);
}
catch (Exception exception)
{
throw exception;
}
}
`
Implement an IAuthorizationDashboardFilter class.
Try this post here.
You need to pass JWT token with each request, try storing token in a cookie or as a query string parameter. Then you can pull that from request context and decide whether user is authorized
public class MyAuthorizationFilter : IDashboardAuthorizationFilter
{
public bool Authorize(DashboardContext context)
{
var httpContext = context.GetHttpContext();
var token = context.Request.Cookies["access-token"];
// check token validity and set authentication accordingly.
return httpContext.User.Identity.IsAuthenticated;
}
}
Register the filter in your OWIN pipeline like this, after whatever authentication method you are using. Then the logged in user claim will be available in the filter
public void Configuration(IAppBuilder app)
{
app.UseCookieAuthentication(...); // Authentication - first
app.UseHangfireDashboard("/hangfire", new DashboardOptions
{
Authorization = new [] { new MyAuthorizationFilter() }
}); // Hangfire - last
}
I am trying to integrate google authentication in my ASP.NET Core 2.0 web api and I cannot figure out how to get it to work.
I have this code in my Startup.cs ConfigureServices:
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddDefaultTokenProviders();
services.AddAuthentication()
.AddGoogle(googleOptions =>
{
googleOptions.ClientId = Configuration["Authentication:Google:ClientId"];
googleOptions.ClientSecret = Configuration["Authentication:Google:ClientSecret"];
});
And this in Configure(IApplicationBuilder app, IHostingEnvironment env):
app.UseAuthentication();
When I navigate to an Authorized endpoint, the result is a 302 Found because presumably it is redirecting to some login endpoint (which I never created). How do I prevent the redirection and just have the API expect a token and return a 401 if no token is provided?
Posting my ultimate approach for posterity.
As Tratcher pointed out, the AddGoogle middleware is not actually for a JWT authentication flow. After doing more research, I realized that what I ultimately wanted is what is described here:
https://developers.google.com/identity/sign-in/web/backend-auth
So my next problems were
I could not rely on the standard dotnet core Jwt auth middleware anymore since I need to delegate the google token validation to google libraries
There was no C# google validator listed as one of the external client libraries on that page.
After more digging, I found this that JWT validation support was added to C# here using this class and method:
Google.Apis.Auth.Task<GoogleJsonWebSignature.Payload> ValidateAsync(string jwt, GoogleJsonWebSignature.ValidationSettings validationSettings)
Next I needed to figure out how to replace the built in JWT validation. From this SO questions I came up with an approach:
ASP.NET Core JWT Bearer Token Custom Validation
Here is my custom GoogleTokenValidator:
public class GoogleTokenValidator : ISecurityTokenValidator
{
private readonly JwtSecurityTokenHandler _tokenHandler;
public GoogleTokenValidator()
{
_tokenHandler = new JwtSecurityTokenHandler();
}
public bool CanValidateToken => true;
public int MaximumTokenSizeInBytes { get; set; } = TokenValidationParameters.DefaultMaximumTokenSizeInBytes;
public bool CanReadToken(string securityToken)
{
return _tokenHandler.CanReadToken(securityToken);
}
public ClaimsPrincipal ValidateToken(string securityToken, TokenValidationParameters validationParameters, out SecurityToken validatedToken)
{
validatedToken = null;
var payload = GoogleJsonWebSignature.ValidateAsync(securityToken, new GoogleJsonWebSignature.ValidationSettings()).Result; // here is where I delegate to Google to validate
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, payload.Name),
new Claim(ClaimTypes.Name, payload.Name),
new Claim(JwtRegisteredClaimNames.FamilyName, payload.FamilyName),
new Claim(JwtRegisteredClaimNames.GivenName, payload.GivenName),
new Claim(JwtRegisteredClaimNames.Email, payload.Email),
new Claim(JwtRegisteredClaimNames.Sub, payload.Subject),
new Claim(JwtRegisteredClaimNames.Iss, payload.Issuer),
};
try
{
var principle = new ClaimsPrincipal();
principle.AddIdentity(new ClaimsIdentity(claims, AuthenticationTypes.Password));
return principle;
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
}
And in Startup.cs, I also needed to clear out the default JWT validation, and add my custom one:
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(o =>
{
o.SecurityTokenValidators.Clear();
o.SecurityTokenValidators.Add(new GoogleTokenValidator());
}
Maybe there is an easier way, but this is where I landed and it seems to work fine! There was additional work I did that I left out of here for simplicity, for example, checking if there is already a user in my user's DB that matches the claims provided by google, so I apologize if the code above does not 100% work since I may have removed something inadvertently.
I just published a NuGet package to handle validation of Google OpenID Connect tokens.
The package relies on Microsoft's JWT validation and authentication handler from Microsoft.AspNetCore.Authentication.JwtBearer, with some added validation around hosted domains.
It contains a single public extension method, UseGoogle, on JwtBearerOptions that lets you configure the handler to validate Google OpenID Connect tokens, without other dependencies:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(jwt => jwt.UseGoogle(
clientId: "<client-id-from-Google-API-console>",
hostedDomain: "<optional-hosted-domain>"));
If you want to take a look at the source, you can find it here.
Mikeyg36's answer was terrific and finally helped me sort out my jwt token issues. However, I added the clientId which I feel is important since you don't want to validate any id token that comes in. I also added "JwtBearerDefaults.AuthenticationScheme" to the AddIdentity.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using Google.Apis.Auth;
namespace Some.Namespace
{
public class GoogleTokenValidator : ISecurityTokenValidator
{
private readonly string _clientId;
private readonly JwtSecurityTokenHandler _tokenHandler;
public GoogleTokenValidator(string clientId)
{
_clientId = clientId;
_tokenHandler = new JwtSecurityTokenHandler();
}
public bool CanValidateToken => true;
public int MaximumTokenSizeInBytes { get; set; } = TokenValidationParameters.DefaultMaximumTokenSizeInBytes;
public bool CanReadToken(string securityToken)
{
return _tokenHandler.CanReadToken(securityToken);
}
public ClaimsPrincipal ValidateToken(string securityToken, TokenValidationParameters validationParameters, out SecurityToken validatedToken)
{
validatedToken = null;
try {
var payload = GoogleJsonWebSignature.ValidateAsync(securityToken, new GoogleJsonWebSignature.ValidationSettings() { Audience = new[] { _clientId }}).Result; // here is where I delegate to Google to validate
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, payload.Name),
new Claim(ClaimTypes.Name, payload.Name),
new Claim(JwtRegisteredClaimNames.FamilyName, payload.FamilyName),
new Claim(JwtRegisteredClaimNames.GivenName, payload.GivenName),
new Claim(JwtRegisteredClaimNames.Email, payload.Email),
new Claim(JwtRegisteredClaimNames.Sub, payload.Subject),
new Claim(JwtRegisteredClaimNames.Iss, payload.Issuer),
};
var principle = new ClaimsPrincipal();
principle.AddIdentity(new ClaimsIdentity(claims, JwtBearerDefaults.AuthenticationScheme));
return principle;
}
catch (Exception e)
{
Debug.WriteLine(e);
throw;
}
}
}
}
I have the following requirements for my C# Web API 2 Service:
The service authenticates Users by the combination of Email and a temporary Passcode that gets sent to their inbox, as a factor of authentication.
I need to blend in this authentication mechanism with producing OAuth bearer tokens to secure the service and use standard ASP.NET Authorisation mechanism to check each request against the token via some kind of [Authorize] attribute.
I have successfully implemented these steps
User requests Passcode
System generates and emails Passcode to User with 30 days expiration
User authenticates with Email + Passcode
System checks validity of Passcode
But I am not sure how to begin implementing the remaining steps
If Passcode valid, system generates OAuth bearer token
OAuth bearer token lasts as long as Passcode expiration date
Use ASP.NET Identity authorization attributes to perform authentication and authorisation checks
Use OWIN Security and OAuth Middleware to create token
Use claims based authorisation and serialise claims into token
The cited process only describes using ASP.NET Identity Individual User accounts as a means to authenticate which is not how I want to authenticate.
http://www.asp.net/web-api/overview/security/individual-accounts-in-web-api
I actually need to authenticate by checking Email and Passcode.
I worked in a similar scenario and had implemented an authentication filter (IAuthenticationFilter) and a customized class inherited from OAuthAuthorizationServerProvider. In my case, I needed to authenticate the request with OAuth and a legacy token. I believe that in your case, you will need customize the AuthenticationFilter. See below an example of the AuthenticationFilter:
public class MyAuthenticationFilter : IAuthenticationFilter
{
private readonly string _authenticationType;
/// <summary>Initializes a new instance of the <see cref="HostAuthenticationFilter"/> class.</summary>
/// <param name="authenticationType">The authentication type of the OWIN middleware to use.</param>
public MyAuthenticationFilter(string authenticationType)
{
if (authenticationType == null)
{
throw new ArgumentNullException("authenticationType");
}
_authenticationType = authenticationType;
}
/// <summary>Gets the authentication type of the OWIN middleware to use.</summary>
public string AuthenticationType
{
get { return _authenticationType; }
}
/// <inheritdoc />
public async Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
HttpRequestMessage request = context.Request;
if (request == null)
{
throw new InvalidOperationException("Request mut not be null");
}
//In my case, i need try autenticate the request with BEARER token (Oauth)
IAuthenticationManager authenticationManager = GetAuthenticationManagerOrThrow(request);
cancellationToken.ThrowIfCancellationRequested();
AuthenticateResult result = await authenticationManager.AuthenticateAsync(_authenticationType);
ClaimsIdentity identity = null;
if (result != null)
{
identity = result.Identity;
if (identity != null)
{
context.Principal = new ClaimsPrincipal(identity);
}
}
else
{
//If havent success with oauth authentication, I need locate the legacy token
//If dont exists the legacy token, set error (will generate http 401)
if (!request.Headers.Contains("legacy-token-header"))
context.ErrorResult = new AuthenticationFailureResult(Resources.SAUTH_ERROR_LEGACYTOKENNOTFOUND, request);
else
{
try
{
var queryString = request.GetQueryNameValuePairs();
if (!queryString.Any(x => x.Key == "l"))
context.ErrorResult = new AuthenticationFailureResult(Resources.SAUTH_ERROR_USERTYPENOTFOUND, request);
else
{
var userType = queryString.First(x => x.Key == "l").Value;
String token = HttpUtility.UrlDecode(request.Headers.GetValues("tk").First());
identity = TokenLegacy.ValidateToken(token, userType);
identity.AddClaims(userType, (OwinRequest) ((OwinContext)context.Request.Properties["MS_OwinContext"]).Request);
if (identity != null)
{
context.Principal = new ClaimsPrincipal(identity);
}
}
}
catch (Exception e)
{
context.ErrorResult = new AuthenticationFailureResult(e.Message, request);
}
}
}
}
/// <inheritdoc />
public Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
HttpRequestMessage request = context.Request;
if (request == null)
{
throw new InvalidOperationException("Request mut not be null");
}
IAuthenticationManager authenticationManager = GetAuthenticationManagerOrThrow(request);
// Control the challenges that OWIN middleware adds later.
authenticationManager.AuthenticationResponseChallenge = AddChallengeAuthenticationType(
authenticationManager.AuthenticationResponseChallenge, _authenticationType);
return TaskHelpers.Completed();
}
/// <inheritdoc />
public bool AllowMultiple
{
get { return true; }
}
private static AuthenticationResponseChallenge AddChallengeAuthenticationType(
AuthenticationResponseChallenge challenge, string authenticationType)
{
Contract.Assert(authenticationType != null);
List<string> authenticationTypes = new List<string>();
AuthenticationProperties properties;
if (challenge != null)
{
string[] currentAuthenticationTypes = challenge.AuthenticationTypes;
if (currentAuthenticationTypes != null)
{
authenticationTypes.AddRange(currentAuthenticationTypes);
}
properties = challenge.Properties;
}
else
{
properties = new AuthenticationProperties();
}
authenticationTypes.Add(authenticationType);
return new AuthenticationResponseChallenge(authenticationTypes.ToArray(), properties);
}
private static IAuthenticationManager GetAuthenticationManagerOrThrow(HttpRequestMessage request)
{
Contract.Assert(request != null);
var owinCtx = request.GetOwinContext();
IAuthenticationManager authenticationManager = owinCtx != null ? owinCtx.Authentication : null;
if (authenticationManager == null)
{
throw new InvalidOperationException("IAuthenticationManagerNotAvailable");
}
return authenticationManager;
}
}
In WebApiConfig.cs, you need the add the authentication filter like this:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Configure Web API to use only bearer token authentication.
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new MyAuthenticationFilter(OAuthDefaults.AuthenticationType));
}
}
I recommend reading the official WEB API poster:
https://www.asp.net/media/4071077/aspnet-web-api-poster.pdf