Simple Authentication for aspnet web api core - c#

I am devloping a web api in .net core framework. Ui is in angularjs. I have multiple users with different roles in the system. What is the simple and best authentication for this web api?
Tried to do Identity server4 but i dont need redirection to single sign on so i don't really need that complex implementation. All i want is to secure the api so that non-logged in/ anonymous users cannot access it.
Things i have tried so far are "Cookie middleware without aspnet identity". Works little bit but the real problem is, i am not sure how to return cookie from api and pass it back from ui.
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/cookie
I looked online but most of the articles are for MVC. Since this is a web api, i need to pass back something to the caller so that they can pass it back to the api which will be authenticated.
Hope it makes sense.
Appreciate your help
Thanks

There is a really good article here which goes into great details about authenticaton and authorization. The article also explains how you can apply authentication to the whole API, to a controller, to a single method etc. All the code for the tutorial is on CodePlex here and I am copying the one for basic authentication below:
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http.Filters;
using BasicAuthentication.Results;
namespace BasicAuthentication.Filters
{
public abstract class BasicAuthenticationAttribute : Attribute, IAuthenticationFilter
{
public string Realm { get; set; }
public async Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
{
HttpRequestMessage request = context.Request;
AuthenticationHeaderValue authorization = request.Headers.Authorization;
if (authorization == null)
{
// No authentication was attempted (for this authentication method).
// Do not set either Principal (which would indicate success) or ErrorResult (indicating an error).
return;
}
if (authorization.Scheme != "Basic")
{
// No authentication was attempted (for this authentication method).
// Do not set either Principal (which would indicate success) or ErrorResult (indicating an error).
return;
}
if (String.IsNullOrEmpty(authorization.Parameter))
{
// Authentication was attempted but failed. Set ErrorResult to indicate an error.
context.ErrorResult = new AuthenticationFailureResult("Missing credentials", request);
return;
}
Tuple<string, string> userNameAndPasword = ExtractUserNameAndPassword(authorization.Parameter);
if (userNameAndPasword == null)
{
// Authentication was attempted but failed. Set ErrorResult to indicate an error.
context.ErrorResult = new AuthenticationFailureResult("Invalid credentials", request);
return;
}
string userName = userNameAndPasword.Item1;
string password = userNameAndPasword.Item2;
IPrincipal principal = await AuthenticateAsync(userName, password, cancellationToken);
if (principal == null)
{
// Authentication was attempted but failed. Set ErrorResult to indicate an error.
context.ErrorResult = new AuthenticationFailureResult("Invalid username or password", request);
}
else
{
// Authentication was attempted and succeeded. Set Principal to the authenticated user.
context.Principal = principal;
}
}
protected abstract Task<IPrincipal> AuthenticateAsync(string userName, string password,
CancellationToken cancellationToken);
private static Tuple<string, string> ExtractUserNameAndPassword(string authorizationParameter)
{
byte[] credentialBytes;
try
{
credentialBytes = Convert.FromBase64String(authorizationParameter);
}
catch (FormatException)
{
return null;
}
// The currently approved HTTP 1.1 specification says characters here are ISO-8859-1.
// However, the current draft updated specification for HTTP 1.1 indicates this encoding is infrequently
// used in practice and defines behavior only for ASCII.
Encoding encoding = Encoding.ASCII;
// Make a writable copy of the encoding to enable setting a decoder fallback.
encoding = (Encoding)encoding.Clone();
// Fail on invalid bytes rather than silently replacing and continuing.
encoding.DecoderFallback = DecoderFallback.ExceptionFallback;
string decodedCredentials;
try
{
decodedCredentials = encoding.GetString(credentialBytes);
}
catch (DecoderFallbackException)
{
return null;
}
if (String.IsNullOrEmpty(decodedCredentials))
{
return null;
}
int colonIndex = decodedCredentials.IndexOf(':');
if (colonIndex == -1)
{
return null;
}
string userName = decodedCredentials.Substring(0, colonIndex);
string password = decodedCredentials.Substring(colonIndex + 1);
return new Tuple<string, string>(userName, password);
}
public Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken)
{
Challenge(context);
return Task.FromResult(0);
}
private void Challenge(HttpAuthenticationChallengeContext context)
{
string parameter;
if (String.IsNullOrEmpty(Realm))
{
parameter = null;
}
else
{
// A correct implementation should verify that Realm does not contain a quote character unless properly
// escaped (precededed by a backslash that is not itself escaped).
parameter = "realm=\"" + Realm + "\"";
}
context.ChallengeWith("Basic", parameter);
}
public virtual bool AllowMultiple
{
get { return false; }
}
}

Related

Webapi 2 custom authorisation does not execute the method

I have created a custom authorisation class to validate the user token. This is web api 2.
Problem is, custom authorisation validate the token but does not execute the method in the controller after. It should execute the user method in the controller after validate the token. I have debug the code and I can see the authorisation token get validated properly but not executing the method and simply return 200.
Can anyone help ? (I am new to this)
custom authorisation class code:
public class CustomAuthorize : System.Web.Http.AuthorizeAttribute
{
public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext)
{
base.OnAuthorization(actionContext);
if (actionContext.Request.Headers.Authorization.Parameter != null)
{
string authenticationToken = Convert.ToString(actionContext.Request.Headers.Authorization.Parameter);
PartnerUserProfile user = new PartnerUserProfile();
user = user.validate_token(authenticationToken);
if (user.recordref > 0) //above user has some content and matches the token from validate_token method. it wil be blank if not
{
return;
}
else
{
HttpContext.Current.Response.AddHeader("Bearer", authenticationToken);
HttpContext.Current.Response.AddHeader("AuthenticationStatus", "NotAuthorized");
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Forbidden);
return;
}
}
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.ExpectationFailed);
actionContext.Response.ReasonPhrase = "Please provide valid inputs";
return;
}
}
and my controller is below this will never get executed.
[HttpPost]
[CustomAuthorize]
public IHttpActionResult user(PartnerUserProfile user) //setUser
{
ReturnData rd = user.setPartnerUserProfile();
if (rd.status == 0)
{
return BadRequest("Invalid");
}
return Ok(rd);
}
When you assign a value to Response, it short circuits and returns right away. The controller logic will only execute if you do not short-circuit (a response is set in an Filter).
https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/filters?view=aspnetcore-2.1#cancellation-and-short-circuiting

Authenticate users in Asp .net Web API

I'm writing API which will be consumed by mobile devices and I want to secure this API end points.
User authentication details is provide by another application called User Manger API (another project which contains user details).
How to make use of ASP.NET Identity framework Authorization and other features to secure my API endpoints while getting the user data from the User manager API ?
The question is a bit broad; basically you are looking for a strategy to authenticate and authorise a client for a web api (dotnet core or normal framework?) using a different existing API (is that API in your control, can you modify it if needed?)
If you can modify both, id say look through StackOverflow and Google for JWT tokens, OAuth and identity server.
1- you can implement an attribute and decorate your api controller.
2- you can implement and register a global filter inside your asp.net's app_start (and make sure you are registering filters in your global.asax).
3- you can do what #Roel-Abspoel mentions implement Identity Server in your User Manager API and have your client talk to it and get the token, then your API talk to it to validate the token.
There are other ways, but i will keep this short and sweet.
Here is an example using an attribute:
using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Security.Principal;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.Http.Filters;
namespace myExample
{
public class ExternalAuthenticationAttribute : IAuthenticationFilter
{
public virtual bool AllowMultiple
{
get { return false; }
}
public Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
{
// get request + authorization headers
HttpRequestMessage request = context.Request;
AuthenticationHeaderValue authorization = request.Headers.Authorization;
// check for username and password (regardless if it was validated on the client, server should check)
// this will only accept Basic Authorization
if (String.IsNullOrEmpty(authorization.Parameter) || authorization.Scheme != "Basic")
{
// Authentication was attempted but failed. Set ErrorResult to indicate an error.
context.ErrorResult = new AuthenticationFailureResult("Missing credentials", request);
return null;
}
var userNameAndPasword = GetCredentials(authorization.Parameter);
if (userNameAndPasword == null)
{
// Authentication was attempted but failed. Set ErrorResult to indicate an error.
context.ErrorResult = new AuthenticationFailureResult("Could not get credentials", request);
return null;
}
// now that we have the username + password call User manager API
var client = new HttpClient();
// POST USERNAME + PASSWORD INSIDE BODY, not header, not query string. ALSO USE HTTPS to make sure it is sent encrypted
var response = AuthenticateAgainstUserMapagerApi1(userNameAndPasword, client);
// THIS WILL WORK IN .NET CORE 1.1. ALSO USE HTTPS to make sure it is sent encrypted
//var response = AuthenticateAgainstUserMapagerApi2(client, userNameAndPasword);
// parse response
if (!response.IsSuccessStatusCode)
{
context.ErrorResult = new AuthenticationFailureResult("Invalid username or password", request);
}
else
{
var readTask = response.Content.ReadAsStringAsync();
var content = readTask.Result;
context.Principal = GetPrincipal(content); // if User manager API returns a user principal as JSON we would
}
return null;
}
//private static HttpResponseMessage AuthenticateAgainstUserMapagerApi2(HttpClient client, Tuple<string, string> userNameAndPasword)
//{
// client.SetBasicAuthentication(userNameAndPasword.Item1, userNameAndPasword.Item2);
// var responseTask = client.GetAsync("https://your_user_manager_api_URL/api/authenticate");
// return responseTask.Result;
//}
private static HttpResponseMessage AuthenticateAgainstUserMapagerApi1(Tuple<string, string> userNameAndPasword, HttpClient client)
{
var credentials = new
{
Username = userNameAndPasword.Item1,
Password = userNameAndPasword.Item2
};
var responseTask = client.PostAsJsonAsync("https://your_user_manager_api_URL/api/authenticate", credentials);
var response = responseTask.Result;
return response;
}
public IPrincipal GetPrincipal(string principalStr)
{
// deserialize principalStr and return a proper Principal instead of ClaimsPrincipal below
return new ClaimsPrincipal();
}
private static Tuple<string, string> GetCredentials(string authorizationParameter)
{
byte[] credentialBytes;
try
{
credentialBytes = Convert.FromBase64String(authorizationParameter);
}
catch (FormatException)
{
return null;
}
try
{
// make sure you use the proper encoding which match client
var encoding = Encoding.ASCII;
string decodedCredentials;
decodedCredentials = encoding.GetString(credentialBytes);
int colonIndex = decodedCredentials.IndexOf(':');
string userName = decodedCredentials.Substring(0, colonIndex);
string password = decodedCredentials.Substring(colonIndex + 1);
return new Tuple<string, string>(userName, password);
}
catch (Exception ex)
{
return null;
}
}
public Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
}
public class AuthenticationFailureResult : IHttpActionResult
{
public AuthenticationFailureResult(string reasonPhrase, HttpRequestMessage request)
{
ReasonPhrase = reasonPhrase;
Request = request;
}
public string ReasonPhrase { get; private set; }
public HttpRequestMessage Request { get; private set; }
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
return Task.FromResult(Execute());
}
private HttpResponseMessage Execute()
{
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
response.RequestMessage = Request;
response.ReasonPhrase = ReasonPhrase;
return response;
}
}
}
use the attribute on your API class like this, which will call User Manager API each time PurchaseController is accessed:
[ExternalAuthenticationAttribute]
public class PurchaseController : ApiController

Get Session from SignalR.AuthorizeAttribute to SignalR.Hub

I am working on a SignalR.Hub and I have custom Authorization in an SignalR.AuthorizeAttribute. I have been trying to pass the session I have to retrieve to confirm the User is Authenticated to use the Hub.
I've looked through all of the properties and it seems that they are mostly read-only. I can add something to SignalR.IRequest.Environment but it doesn't appear to be thread-safe and seems improper.
Could I extend the HubCallerContext + Everything that uses it in a way I can tack on my session?
The custom auth
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
public class HubAuthorize : AuthorizeAttribute
{
public override bool AuthorizeHubConnection(HubDescriptor hubDescriptor, IRequest request)
{
return VerifySession(request);
}
public override bool AuthorizeHubMethodInvocation(IHubIncomingInvokerContext hubIncomingInvokerContext, bool appliesToMethod)
{
//Could I add something to the HubIncomingInvokerContext?
return VerifySession(hubIncomingInvokerContext.Hub.Context.Request);
}
public bool VerifySession(IRequest request)
{
bool success = false;
string token = "";
bool isApiToken = false;
// Check for token Header Auth
if (string.IsNullOrWhiteSpace(token))
{
token = request.QueryString["X-Custom-Token"];
if (string.IsNullOrWhiteSpace(token))
{
token = request.QueryString["X-Custom-Token"];
isApiToken = true;
}
}
SessionResponse session = null;
if (!string.IsNullOrWhiteSpace(token))
{
session = isApiToken ? ValidateApiToken(token) : ValidateToken(token);
}
if (session != null)
{
//Add Session to request! So I dont have to hit the db again..
//request.Add(new KeyValuePair<string, object>("session", session));
success = true;
}
return success;
}
//... other methods that aren't relevant
}
The Hub
[HubAuthorize]
public class NotificationHub : Hub
{
public void Send(string name, string message)
{
// Use the session here
Clients.All.broadcastMessage(name, message);
}
public override Task OnConnected()
{
Console.WriteLine(Context.ConnectionId);
return base.OnConnected();
}
}
Why have custom authorization if you cant use that to retrieve a session when you verify you are authenticated? Maybe I'm missing something but its pretty frustrating. /endrant
Seems you can't. I still verify the session in HubAuthorize.AuthorizeHubConnection, then in the NotificationHub.OnConnected I build a dictionary of Context.ConnectionId as the key and retrieve the session a secondary time for the value. Feels hackish but there doesn't seem to be a good way to prevent people from accessing the hub without using the .Net built in auth.

How to get error message returned by DotNetOpenAuth.OAuth2 on client side?

I'm using ExchangeUserCredentialForToken function to get the token from the Authorization server. It's working fine when my user exists in my databas, but when the credentials are incorect I would like to send back a message to the client. I'm using the following 2 lines of code to set the error message:
context.SetError("Autorization Error", "The username or password is incorrect!");
context.Rejected();
But on the client side I'm getting only protocol error (error 400). Can you help me how can I get the error message set on the server side on the authorization server?
The full app config from the Authorization server:
using Constants;
using Microsoft.Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.Infrastructure;
using Microsoft.Owin.Security.OAuth;
using Owin;
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Security.Claims;
using System.Security.Principal;
using System.Threading.Tasks;
using AuthorizationServer.Entities;
using AuthorizationServer.Entities.Infrastructure.Abstract;
using AuthorizationServer.Entities.Infrastructure.Concrete;
namespace AuthorizationServer
{
public partial class Startup
{
private IEmployeeRepository Repository;
public void ConfigureAuth(IAppBuilder app)
{
//instanciate the repository
Repository = new EmployeeRepository();
// Enable Application Sign In Cookie
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Application",
AuthenticationMode = AuthenticationMode.Passive,
LoginPath = new PathString(Paths.LoginPath),
LogoutPath = new PathString(Paths.LogoutPath),
});
// Enable External Sign In Cookie
app.SetDefaultSignInAsAuthenticationType("External");
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "External",
AuthenticationMode = AuthenticationMode.Passive,
CookieName = CookieAuthenticationDefaults.CookiePrefix + "External",
ExpireTimeSpan = TimeSpan.FromMinutes(5),
});
// Enable google authentication
app.UseGoogleAuthentication();
// Setup Authorization Server
app.UseOAuthAuthorizationServer(new OAuthAuthorizationServerOptions
{
AuthorizeEndpointPath = new PathString(Paths.AuthorizePath),
TokenEndpointPath = new PathString(Paths.TokenPath),
ApplicationCanDisplayErrors = true,
#if DEBUG
AllowInsecureHttp = true,
#endif
// Authorization server provider which controls the lifecycle of Authorization Server
Provider = new OAuthAuthorizationServerProvider
{
OnValidateClientRedirectUri = ValidateClientRedirectUri,
OnValidateClientAuthentication = ValidateClientAuthentication,
OnGrantResourceOwnerCredentials = GrantResourceOwnerCredentials,
OnGrantClientCredentials = GrantClientCredetails
},
// Authorization code provider which creates and receives authorization code
AuthorizationCodeProvider = new AuthenticationTokenProvider
{
OnCreate = CreateAuthenticationCode,
OnReceive = ReceiveAuthenticationCode,
},
// Refresh token provider which creates and receives referesh token
RefreshTokenProvider = new AuthenticationTokenProvider
{
OnCreate = CreateRefreshToken,
OnReceive = ReceiveRefreshToken,
}
});
// indicate our intent to use bearer authentication
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions
{
AuthenticationType = "Bearer",
AuthenticationMode = Microsoft.Owin.Security.AuthenticationMode.Active
});
}
private Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context)
{
if (context.ClientId == Clients.Client1.Id)
{
context.Validated(Clients.Client1.RedirectUrl);
}
else if (context.ClientId == Clients.Client2.Id)
{
context.Validated(Clients.Client2.RedirectUrl);
}
return Task.FromResult(0);
}
private Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
string clientname;
string clientpassword;
if (context.TryGetBasicCredentials(out clientname, out clientpassword) ||
context.TryGetFormCredentials(out clientname, out clientpassword))
{
employee Employee = Repository.GetEmployee(clientname, clientpassword);
if (Employee != null)
{
context.Validated();
}
else
{
context.SetError("Autorization Error", "The username or password is incorrect!");
context.Rejected();
}
}
return Task.FromResult(0);
}
private Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var identity = new ClaimsIdentity(new GenericIdentity(context.UserName, OAuthDefaults.AuthenticationType), context.Scope.Select(x => new Claim("urn:oauth:scope", x)));
context.Validated(identity);
return Task.FromResult(0);
}
private Task GrantClientCredetails(OAuthGrantClientCredentialsContext context)
{
var identity = new ClaimsIdentity(new GenericIdentity(context.ClientId, OAuthDefaults.AuthenticationType), context.Scope.Select(x => new Claim("urn:oauth:scope", x)));
context.Validated(identity);
return Task.FromResult(0);
}
private readonly ConcurrentDictionary<string, string> _authenticationCodes =
new ConcurrentDictionary<string, string>(StringComparer.Ordinal);
private void CreateAuthenticationCode(AuthenticationTokenCreateContext context)
{
context.SetToken(Guid.NewGuid().ToString("n") + Guid.NewGuid().ToString("n"));
_authenticationCodes[context.Token] = context.SerializeTicket();
}
private void ReceiveAuthenticationCode(AuthenticationTokenReceiveContext context)
{
string value;
if (_authenticationCodes.TryRemove(context.Token, out value))
{
context.DeserializeTicket(value);
}
}
private void CreateRefreshToken(AuthenticationTokenCreateContext context)
{
context.SetToken(context.SerializeTicket());
}
private void ReceiveRefreshToken(AuthenticationTokenReceiveContext context)
{
context.DeserializeTicket(context.Token);
}
}
}
After hours of searching the web and reading blobs, and the owin documentation, I have found a way to return a 401 for a failed login attempt.
I realize adding the header below is a bit of a hack, but I could not find any way to read the IOwinContext.Response.Body stream to look for the error message.
First of all, In the OAuthAuthorizationServerProvider.GrantResourceOwnerCredentials I used SetError() and added a Headers to the response
context.SetError("Autorization Error", "The username or password is incorrect!");
context.Response.Headers.Add("AuthorizationResponse", new[] { "Failed" });
Now, you have a way to differentiate between a 400 error for a failed athentication request, and a 400 error caused by something else.
The next step is to create a class that inherits OwinMiddleware. This class checks the outgoing response and if the StatusCode == 400 and the Header above is present, it changes the StatucCode to 401.
public class InvalidAuthenticationMiddleware : OwinMiddleware
{
public InvalidAuthenticationMiddleware(OwinMiddleware next)
: base(next)
{
}
public override async Task Invoke(IOwinContext context)
{
await Next.Invoke(context);
if (context.Response.StatusCode == 400 && context.Response.Headers.ContainsKey("AuthorizationResponse"))
{
context.Response.Headers.Remove("AuthorizationResponse");
context.Response.StatusCode = 401;
}
}
}
The last thing to do is in your Startup.Configuration method, register the class you just created. I registered it before I did anything else in the method.
app.Use<InvalidAuthenticationMiddleware>();
Here is a full solution, using Jeff's concepts in conjunction with my original post.
1) Setting the error message in the context
If you call context.Rejected() after you have set the error message, then the error message is removed (see example below):
context.SetError("Account locked",
"You have exceeded the total allowed failed logins. Please try back in an hour.");
context.Rejected();
You will want to remove the context.Rejected() from your Task. Please note the definitions of the Rejected and SetError methods are:
Rejected:
Marks this context as not validated by the application. IsValidated and HasError become false as a result of calling.
SetError:
Marks this context as not validated by the application and assigns various error information properties. HasError becomes true and IsValidated becomes false as a result of calling.
Again, by calling the Rejected method after you set the error, the context will be marked as not having an error and the error message will be removed.
2) Setting the status code of the response: Using Jeff's example, with a bit of a spin on it.
Instead of using a magic string, I would create a global property for setting the tag for the status code. In your static global class, create a property for flagging the status code (I used X-Challenge, but you of course could use whatever you choose.) This will be used to flag the header property that is added in the response.
public static class ServerGlobalVariables
{
//Your other properties...
public const string OwinChallengeFlag = "X-Challenge";
}
Then in the various tasks of your OAuthAuthorizationServerProvider, you will add the tag as the key to a new header value in the response. Using the HttpStatusCode enum in conjunction with you global flag, you will have access to all of the various status codes and you avoid a magic string.
//Set the error message
context.SetError("Account locked",
"You have exceeded the total allowed failed logins. Please try back in an hour.");
//Add your flag to the header of the response
context.Response.Headers.Add(ServerGlobalVariables.OwinChallengeFlag,
new[] { ((int)HttpStatusCode.Unauthorized).ToString() });
In the customer OwinMiddleware, you can search for the flag in the header using the global variable:
//This class handles all the OwinMiddleware responses, so the name should
//not just focus on invalid authentication
public class CustomAuthenticationMiddleware : OwinMiddleware
{
public CustomAuthenticationMiddleware(OwinMiddleware next)
: base(next)
{
}
public override async Task Invoke(IOwinContext context)
{
await Next.Invoke(context);
if (context.Response.StatusCode == 400
&& context.Response.Headers.ContainsKey(
ServerGlobalVariables.OwinChallengeFlag))
{
var headerValues = context.Response.Headers.GetValues
(ServerGlobalVariables.OwinChallengeFlag);
context.Response.StatusCode =
Convert.ToInt16(headerValues.FirstOrDefault());
context.Response.Headers.Remove(
ServerGlobalVariables.OwinChallengeFlag);
}
}
}
Finally, as Jeff pointed out, you have to register this custom OwinMiddleware in your Startup.Configuration or Startup.ConfigureAuth method:
app.Use<CustomAuthenticationMiddleware>();
Using the above solution, you can now set the status codes and a custom error message, like the ones shown below:
Invalid user name or password
This account has exceeded the maximum number of attempts
The email account has not been confirmed
3) Extracting the error message from the ProtocolException
In the client application, a ProtocolException will need to be caught and processed. Something like this will give you the answer:
//Need to create a class to deserialize the Json
//Create this somewhere in your application
public class OAuthErrorMsg
{
public string error { get; set; }
public string error_description { get; set; }
public string error_uri { get; set; }
}
//Need to make sure to include Newtonsoft.Json
using Newtonsoft.Json;
//Code for your object....
private void login()
{
try
{
var state = _webServerClient.ExchangeUserCredentialForToken(
this.emailTextBox.Text,
this.passwordBox.Password.Trim(),
scopes: new string[] { "PublicProfile" });
_accessToken = state.AccessToken;
_refreshToken = state.RefreshToken;
}
catch (ProtocolException ex)
{
var webException = ex.InnerException as WebException;
OAuthErrorMsg error =
JsonConvert.DeserializeObject<OAuthErrorMsg>(
ExtractResponseString(webException));
var errorMessage = error.error_description;
//Now it's up to you how you process the errorMessage
}
}
public static string ExtractResponseString(WebException webException)
{
if (webException == null || webException.Response == null)
return null;
var responseStream =
webException.Response.GetResponseStream() as MemoryStream;
if (responseStream == null)
return null;
var responseBytes = responseStream.ToArray();
var responseString = Encoding.UTF8.GetString(responseBytes);
return responseString;
}
I have tested this and it works perfectly in VS2013 Pro with 4.5!!
(please note, I did not include all the necessary namespaces or the additional code since this will vary depending on the application: WPF, MVC, or Winform. Also, I didn't discuss error handling, so you will want to make sure to implement proper error handling throughout your solution.)
Jeff's solution does not work for me, but when I use OnSendingHeaders it works fine:
public class InvalidAuthenticationMiddleware : OwinMiddleware
{
public InvalidAuthenticationMiddleware(OwinMiddleware next) : base(next) { }
public override async Task Invoke(IOwinContext context)
{
context.Response.OnSendingHeaders(state =>
{
var response = (OwinResponse)state;
if (!response.Headers.ContainsKey("AuthorizationResponse") && response.StatusCode != 400) return;
response.Headers.Remove("AuthorizationResponse");
response.StatusCode = 401;
}, context.Response);
await Next.Invoke(context);
}
}

Return error on invalid or expired token

I'm trying to implement OAuth Bearer Authentication with Owin. When an invalid or expired token is passed, the default implementation is to log this as a warning and just don't set an Identity. I however would like to reject the whole request with an error in this case. But how would I do this?
After digging through the code I found out that in OAuthBearerAuthenticationHandler it will parse the token using a fallback mechanism when the provided AuthenticationTokenProvider did not parse any ticket (like the default implementation). This handler will log a warning when the token could not be parsed to any ticket or when it expired.
But I can't find any place to plug in my own logic to what happens when the token is invalid or expired. I could theoretically check this on my own in the AuthenticationTokenProvider, but then I would have to reimplement the logic (= copy it over) for creating and reading the token. Also this seems just out of place, as this class seems to be only responsible for creating and parsing tokens. I also don't see a way to plug in my own implementation of the OAuthBearerAuthenticationHandler in the OAuthBearerAuthenticationMiddleware.
Apparently my best and cleanest shot would be to reimplement the whole middleware, but this also seems very overkill.
What do I overlook? How would I go on about this the best?
edit:
For clarification. I know by not setting an identity the request will be rejected with 401 Unauthorized later in the Web API. But I personally see this as really bad style, silently swallowing an erroneous access token without any notification. This way you don't get to know that your token is crap, you just get to know you're not authorized.
I had a similar issue, i think the answer is to late but someone will come here with a similar problem:
I used this nuget package for validate authentication, but i think any method can help: https://www.nuget.org/packages/WebApi.AuthenticationFilter. You can read its documentation in this site https://github.com/mbenford/WebApi-AuthenticationFilter
AuthenticationFilter.cs
public class AuthenticationFilter : AuthenticationFilterAttribute{
public override void OnAuthentication(HttpAuthenticationContext context)
{
System.Net.Http.Formatting.MediaTypeFormatter jsonFormatter = new System.Net.Http.Formatting.JsonMediaTypeFormatter();
var ci = context.Principal.Identity as ClaimsIdentity;
//First of all we are going to check that the request has the required Authorization header. If not set the Error
var authHeader = context.Request.Headers.Authorization;
//Change "Bearer" for the needed schema
if (authHeader == null || authHeader.Scheme != "Bearer")
{
context.ErrorResult = context.ErrorResult = new AuthenticationFailureResult("unauthorized", context.Request,
new { Error = new { Code = 401, Message = "Request require authorization" } });
}
//If the token has expired the property "IsAuthenticated" would be False, then set the error
else if (!ci.IsAuthenticated)
{
context.ErrorResult = new AuthenticationFailureResult("unauthorized", context.Request,
new { Error = new { Code = 401, Message = "The Token has expired" } });
}
}}
AuthenticationFailureResult.cs
public class AuthenticationFailureResult : IHttpActionResult{
private object ResponseMessage;
public AuthenticationFailureResult(string reasonPhrase, HttpRequestMessage request, object responseMessage)
{
ReasonPhrase = reasonPhrase;
Request = request;
ResponseMessage = responseMessage;
}
public string ReasonPhrase { get; private set; }
public HttpRequestMessage Request { get; private set; }
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
return Task.FromResult(Execute());
}
private HttpResponseMessage Execute()
{
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
System.Net.Http.Formatting.MediaTypeFormatter jsonFormatter = new System.Net.Http.Formatting.JsonMediaTypeFormatter();
response.Content = new System.Net.Http.ObjectContent<object>(ResponseMessage, jsonFormatter);
response.RequestMessage = Request;
response.ReasonPhrase = ReasonPhrase;
return response;
}}
Response examples:
{"Error":{"Code":401,"Message":"Request require authorization"}}
{"Error":{"Code":401,"Message":"The Token has expired"}}
Fonts and inspiration documentation:
//github.com/mbenford/WebApi-AuthenticationFilter
//www.asp.net/web-api/overview/security/authentication-filters
Yeah, I did not find 'good' solution for this,
I also don't see a way to plug in my own implementation of the
OAuthBearerAuthenticationHandler in the
OAuthBearerAuthenticationMiddleware.
Apparently my best and cleanest shot would be to reimplement the whole
middleware, but this also seems very overkill.
Agreed, but that's what I did (before reading your post). I copy & pasted three owin classes, and made it so that it sets property in Owins context, which can be later checked by other handlers.
public static class OAuthBearerAuthenticationExtensions
{
public static IAppBuilder UseOAuthBearerAuthenticationExtended(this IAppBuilder app, OAuthBearerAuthenticationOptions options)
{
if (app == null)
throw new ArgumentNullException(nameof(app));
app.Use(typeof(OAuthBearerAuthenticationMiddlewareExtended), app, options);
app.UseStageMarker(PipelineStage.Authenticate);
return app;
}
}
internal class OAuthBearerAuthenticationHandlerExtended : AuthenticationHandler<OAuthBearerAuthenticationOptions>
{
private readonly ILogger _logger;
private readonly string _challenge;
public OAuthBearerAuthenticationHandlerExtended(ILogger logger, string challenge)
{
_logger = logger;
_challenge = challenge;
}
protected override async Task<AuthenticationTicket> AuthenticateCoreAsync()
{
try
{
// Find token in default location
string requestToken = null;
string authorization = Request.Headers.Get("Authorization");
if (!string.IsNullOrEmpty(authorization))
{
if (authorization.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
{
requestToken = authorization.Substring("Bearer ".Length).Trim();
}
}
// Give application opportunity to find from a different location, adjust, or reject token
var requestTokenContext = new OAuthRequestTokenContext(Context, requestToken);
await Options.Provider.RequestToken(requestTokenContext);
// If no token found, no further work possible
if (string.IsNullOrEmpty(requestTokenContext.Token))
{
return null;
}
// Call provider to process the token into data
var tokenReceiveContext = new AuthenticationTokenReceiveContext(
Context,
Options.AccessTokenFormat,
requestTokenContext.Token);
await Options.AccessTokenProvider.ReceiveAsync(tokenReceiveContext);
if (tokenReceiveContext.Ticket == null)
{
tokenReceiveContext.DeserializeTicket(tokenReceiveContext.Token);
}
AuthenticationTicket ticket = tokenReceiveContext.Ticket;
if (ticket == null)
{
_logger.WriteWarning("invalid bearer token received");
Context.Set("oauth.token_invalid", true);
return null;
}
// Validate expiration time if present
DateTimeOffset currentUtc = Options.SystemClock.UtcNow;
if (ticket.Properties.ExpiresUtc.HasValue &&
ticket.Properties.ExpiresUtc.Value < currentUtc)
{
_logger.WriteWarning("expired bearer token received");
Context.Set("oauth.token_expired", true);
return null;
}
// Give application final opportunity to override results
var context = new OAuthValidateIdentityContext(Context, Options, ticket);
if (ticket != null &&
ticket.Identity != null &&
ticket.Identity.IsAuthenticated)
{
// bearer token with identity starts validated
context.Validated();
}
if (Options.Provider != null)
{
await Options.Provider.ValidateIdentity(context);
}
if (!context.IsValidated)
{
return null;
}
// resulting identity values go back to caller
return context.Ticket;
}
catch (Exception ex)
{
_logger.WriteError("Authentication failed", ex);
return null;
}
}
protected override Task ApplyResponseChallengeAsync()
{
if (Response.StatusCode != 401)
{
return Task.FromResult<object>(null);
}
AuthenticationResponseChallenge challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode);
if (challenge != null)
{
OAuthChallengeContext challengeContext = new OAuthChallengeContext(Context, _challenge);
Options.Provider.ApplyChallenge(challengeContext);
}
return Task.FromResult<object>(null);
}
}
public class OAuthBearerAuthenticationMiddlewareExtended : AuthenticationMiddleware<OAuthBearerAuthenticationOptions>
{
private readonly ILogger _logger;
private readonly string _challenge;
/// <summary>
/// Bearer authentication component which is added to an OWIN pipeline. This constructor is not
/// called by application code directly, instead it is added by calling the the IAppBuilder UseOAuthBearerAuthentication
/// extension method.
///
/// </summary>
public OAuthBearerAuthenticationMiddlewareExtended(OwinMiddleware next, IAppBuilder app, OAuthBearerAuthenticationOptions options)
: base(next, options)
{
_logger = AppBuilderLoggerExtensions.CreateLogger<OAuthBearerAuthenticationMiddlewareExtended>(app);
_challenge = string.IsNullOrWhiteSpace(Options.Challenge) ? (!string.IsNullOrWhiteSpace(Options.Realm) ? "Bearer realm=\"" + this.Options.Realm + "\"" : "Bearer") : this.Options.Challenge;
if (Options.Provider == null)
Options.Provider = new OAuthBearerAuthenticationProvider();
if (Options.AccessTokenFormat == null)
Options.AccessTokenFormat = new TicketDataFormat(
Microsoft.Owin.Security.DataProtection.AppBuilderExtensions.CreateDataProtector(app, typeof(OAuthBearerAuthenticationMiddleware).Namespace, "Access_Token", "v1"));
if (Options.AccessTokenProvider != null)
return;
Options.AccessTokenProvider = new AuthenticationTokenProvider();
}
/// <summary>
/// Called by the AuthenticationMiddleware base class to create a per-request handler.
///
/// </summary>
///
/// <returns>
/// A new instance of the request handler
/// </returns>
protected override AuthenticationHandler<OAuthBearerAuthenticationOptions> CreateHandler()
{
return new OAuthBearerAuthenticationHandlerExtended(_logger, _challenge);
}
}
Then I wrote my own authorization filter, which will be applied globally:
public class AuthorizeAttributeExtended : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(HttpActionContext actionContext)
{
var tokenHasExpired = false;
var owinContext = OwinHttpRequestMessageExtensions.GetOwinContext(actionContext.Request);
if (owinContext != null)
{
tokenHasExpired = owinContext.Environment.ContainsKey("oauth.token_expired");
}
if (tokenHasExpired)
{
actionContext.Response = new AuthenticationFailureMessage("unauthorized", actionContext.Request,
new
{
error = "invalid_token",
error_message = "The Token has expired"
});
}
else
{
actionContext.Response = new AuthenticationFailureMessage("unauthorized", actionContext.Request,
new
{
error = "invalid_request",
error_message = "The Token is invalid"
});
}
}
}
public class AuthenticationFailureMessage : HttpResponseMessage
{
public AuthenticationFailureMessage(string reasonPhrase, HttpRequestMessage request, object responseMessage)
: base(HttpStatusCode.Unauthorized)
{
MediaTypeFormatter jsonFormatter = new JsonMediaTypeFormatter();
Content = new ObjectContent<object>(responseMessage, jsonFormatter);
RequestMessage = request;
ReasonPhrase = reasonPhrase;
}
}
my WebApiConfig:
config.Filters.Add(new AuthorizeAttributeExtended());
How my configureOAuth looks like:
public void ConfigureOAuth(IAppBuilder app)
{
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
OAuthBearerOptions = new OAuthBearerAuthenticationOptions()
{
};
OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromSeconds(10),
Provider = new SimpleAuthorizationServerProvider(),
RefreshTokenProvider = new SimpleRefreshTokenProvider(),
AuthenticationMode = AuthenticationMode.Active
};
FacebookAuthOptions = new CustomFacebookAuthenticationOptions();
app.UseFacebookAuthentication(FacebookAuthOptions);
app.UseOAuthAuthorizationServer(OAuthServerOptions);
app.UseOAuthBearerAuthenticationExtended(OAuthBearerOptions);
}
I will try & get this to main branch of oAuth middleware, it seems like an obvious use case, unless I am missing something.
I came across this problem recently. We wanted to return a JSON message if the user's access token had expired, allowing the consumer web application to silently refresh the access token and re-issue the API request. We also didn't want to rely on the exceptions thrown for token lifetime validation.
Not wanting to re-implement any middleware, we specified the Provider option inside JwtBearerAuthenticationOptions and added a delegate to handle the OnRequestTokenMethod. The delegate checks to see if it can read the token passed to the middleware and sets a boolean inside the OWIN context if it's expired.
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
TokenValidationParameters = tokenValidationParameters,
Provider = new OAuthBearerAuthenticationProvider
{
OnRequestToken = (ctx) =>
{
if (!string.IsNullOrEmpty(ctx.Token))
{
JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
if (handler.CanReadToken(ctx.Token))
{
JwtSecurityToken jwtToken = handler.ReadJwtToken(ctx.Token);
if (jwtToken.IsExpired())
ctx.OwinContext.Set<bool>("expiredToken", true);
}
}
return Task.CompletedTask;
}
}
});
For convenience I added a quick extension method to check if a JWT expired:
public static class JwtSecurityTokenExtensions
{
public static bool IsExpired (this JwtSecurityToken token)
{
if (DateTime.UtcNow > token.ValidTo.ToUniversalTime())
return true;
return false;
}
}
We ended up using a middleware to check on the state of that boolean:
app.Use((context, next) =>
{
bool expiredToken = context.Get<bool>("expiredToken");
if (expiredToken)
{
// do stuff
}
return next.Invoke();
});
app.UseStageMarker(PipelineStage.Authenticate);
Not exactly the most efficient code, since we're parsing the token again after the middleware already did and also introducing a new middleware to act on the result of the check, but it's a fresh perspective nonetheless.
If authentication fails (meaning the token is expired) then that layer doesn't set the user, as you said. It's up the the authorization layer (later on) to reject the call. So for your scenario your Web API would need to deny access to an anonymous caller. Use the [Authorize] authorization filter attribute.

Categories