Adding additional logic to Bearer authorization - c#

I am attempting to implement OWIN bearer token authorization, and based on this article. However, there's one additional piece of information I need in bearer token that I don't know how to implement.
In my application, I need to deduce from the bearer token user information (say userid). This is important because I don't want an authorized user from being able to act as another user. Is this doable? Is it even the correct approach? If the userid is a guid, then this would be simple. It's an integer in this case.
An authorized user can potentially impersonate another just by guessing / brute force, which is unacceptable.
Looking at this code:
public void ConfigureOAuth(IAppBuilder app)
{
OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = new SimpleAuthorizationServerProvider()
};
// Token Generation
app.UseOAuthAuthorizationServer(OAuthServerOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}
public class SimpleAuthorizationServerProvider : OAuthAuthorizationServerProvider
{
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated();
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
using (AuthRepository _repo = new AuthRepository())
{
IdentityUser user = await _repo.FindUser(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
}
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim("sub", context.UserName));
identity.AddClaim(new Claim("role", "user"));
context.Validated(identity);
}
}
I would think that it is possible to override the authorization / authentication to accommodate what I need?

It seems there's something missing in your code.
You're not validating your client.
You should implement ValidateClientAuthentication and check your client's credentials there.
This is what I do:
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
string clientId = string.Empty;
string clientSecret = string.Empty;
if (!context.TryGetBasicCredentials(out clientId, out clientSecret))
{
context.SetError("invalid_client", "Client credentials could not be retrieved through the Authorization header.");
context.Rejected();
return;
}
ApplicationDatabaseContext dbContext = context.OwinContext.Get<ApplicationDatabaseContext>();
ApplicationUserManager userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
if (dbContext == null)
{
context.SetError("server_error");
context.Rejected();
return;
}
try
{
AppClient client = await dbContext
.Clients
.FirstOrDefaultAsync(clientEntity => clientEntity.Id == clientId);
if (client != null && userManager.PasswordHasher.VerifyHashedPassword(client.ClientSecretHash, clientSecret) == PasswordVerificationResult.Success)
{
// Client has been verified.
context.OwinContext.Set<AppClient>("oauth:client", client);
context.Validated(clientId);
}
else
{
// Client could not be validated.
context.SetError("invalid_client", "Client credentials are invalid.");
context.Rejected();
}
}
catch (Exception ex)
{
string errorMessage = ex.Message;
context.SetError("server_error");
context.Rejected();
}
}
A good article full of details can be found here.
A even better explanation can be found in this blog series.
UPDATE:
I did some digging and webstuff is right.
In order to pass errorDescription to the client we need to Rejected before we set the error with SetError:
context.Rejected();
context.SetError("invalid_client", "The information provided are not valid !");
return;
or we can extend it passing a serialized json object in the description:
context.Rejected();
context.SetError("invalid_client", Newtonsoft.Json.JsonConvert.SerializeObject(new { result = false, message = "The information provided are not valid !" }));
return;
With a javascript/jQuery client we could deserialize the text response and read the extended message:
$.ajax({
type: 'POST',
url: '<myAuthorizationServer>',
data: { username: 'John', password: 'Smith', grant_type: 'password' },
dataType: "json",
contentType: 'application/x-www-form-urlencoded; charset=utf-8',
xhrFields: {
withCredentials: true
},
headers: {
'Authorization': 'Basic ' + authorizationBasic
},
error: function (req, status, error) {
if (req.responseJSON && req.responseJSON.error_description)
{
var error = $.parseJSON(req.responseJSON.error_description);
alert(error.message);
}
}
});

On a side note, if you want to set a custom error message you'll have to swap the order of the context.Rejected and context.SetError.
// Summary:
// Marks this context as not validated by the application. IsValidated and HasError
// become false as a result of calling.
public virtual void Rejected();
If you place context.Rejected after context.SetError then the property context.HasError will be reset to false therefore the correct way to use it is:
// Client could not be validated.
context.Rejected();
context.SetError("invalid_client", "Client credentials are invalid.");

Just to add on to LeftyX's answer, here's how you can completely control the response being sent to the client once the context is rejected. Pay attention to the code comments.
Based on Greg P's original answer, with some modifications
Step1: Create a class which will act as your middleware
using AppFunc = System.Func<System.Collections.Generic.IDictionary<string, System.Object>,
System.Threading.Tasks.Task>;
namespace SignOnAPI.Middleware.ResponseMiddleware
{
public class ResponseMiddleware
{
AppFunc _next;
ResponseMiddlewareOptions _options;
public ResponseMiddleware(AppFunc nex, ResponseMiddlewareOptions options)
{
_next = next;
}
public async Task Invoke(IDictionary<string, object> environment)
{
var context = new OwinContext(environment);
await _next(environment);
if (context.Response.StatusCode == 400 && context.Response.Headers.ContainsKey("Change_Status_Code"))
{
//read the status code sent in the response
var headerValues = context.Response.Headers.GetValues("Change_Status_Code");
//replace the original status code with the new one
context.Response.StatusCode = Convert.ToInt16(headerValues.FirstOrDefault());
//remove the unnecessary header flag
context.Response.Headers.Remove("Change_Status_Code");
}
}
}
Step2 : Create the extensions class (Can be omitted).
This step is optional, can be modified to accept options that can be passed to the middleware.
public static class ResponseMiddlewareExtensions
{
//method name that will be used in the startup class, add additional parameter to accept middleware options if necessary
public static void UseResponseMiddleware(this IAppBuilder app)
{
app.Use<ResponseMiddleware>();
}
}
Step3: Modify GrantResourceOwnerCredentials method in your OAuthAuthorizationServerProvider implementation
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
if (<logic to validate username and password>)
{
//first reject the context, to signify that the client is not valid
context.Rejected();
//set the error message
context.SetError("invalid_username_or_password", "Invalid userName or password" );
//add a new key in the header along with the statusCode you'd like to return
context.Response.Headers.Add("Change_Status_Code", new[] { ((int)HttpStatusCode.Unauthorized).ToString() });
return;
}
}
Step4: Use this middleware in the startup class
public void Configuration(IAppBuilder app)
{
app.UseResponseMiddleware();
//configure the authentication server provider
ConfigureOAuth(app);
//rest of your code goes here....
}

Related

I get cookie instead of token with authorization code grant

Summary
I have ASP.NET MVC 5 web app with Identity authentication and I have to develop an API with "grant_type" = "authorization_code". This API will be to provide users data to another "well-known" web service that needs a custom error responses. My IDE is Visual Studio Professional 2017. I use Postman to make requests to my Web API.
Documentation I read
In the OWIN and Katana documentation the OWIN OAuth 2.0 Authorization Server link redirects again to main OWIN and Katana page, but I think that I found the source on GitHub: OWIN OAuth 2.0 Authorization Server. I tried to follow this documentation, but there are no examples about this question.
Problem
I can create a new authorization code in my AuthorizationCodeProvider class (with Create() method) when a user authenticates and authorizes the "well-known" web service client to access user's resources. I store this code in a database. When I request a Token AuthorizationCodeProvider.Receive() method is called and the token is deserialized correctly. Then GrantAuthorizationCode() method is called, Postman receives OK response (200 status code) but without token information in body (.AspNet.ApplicationCookie is in cookies).
Detailed explanation and code
This is the Startup class:
public partial class Startup
{
public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }
public void ConfigureAuth(IAppBuilder app)
{
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager)),
OnApplyRedirect = (context =>
{
// This code is to return custom error response
string path = null;
if (context.Request.Path.HasValue)
path = context.Request.Path.Value;
if (!(path != null && path.Contains("/api"))) // Don't redirect to login page
context.Response.Redirect(context.RedirectUri);
})
}
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));
app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
this.ConfigureAuthorization(app);
}
private void ConfigureAuthorization(IAppBuilder app)
{
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
OAuthOptions = new OAuthAuthorizationServerOptions
{
AllowInsecureHttp = false,
TokenEndpointPath = new PathString("/api/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = new TokenAuthorizationServerProvider(),
AuthorizationCodeProvider = new AuthorizationCodeProvider()
};
app.Use<AuthenticationMiddleware>(); //Customize responses in Token middleware
app.UseOAuthAuthorizationServer(OAuthOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}
}
ConfigureAuthorization() method configures the authorization. It uses classes implemented by me:
AuthenticationMiddleware: the well-known web service wants 401 status responses with custom error JONS instead of the usual 400 status response. It is based on the answer of the question Replace response body using owin middleware.
public class AuthenticationMiddleware : OwinMiddleware
{
public AuthenticationMiddleware(OwinMiddleware next) : base(next) { }
public override async Task Invoke(IOwinContext context)
{
var owinResponse = context.Response;
var owinResponseStream = owinResponse.Body;
var responseBuffer = new MemoryStream();
owinResponse.Body = responseBuffer;
await Next.Invoke(context);
if (context.Response.StatusCode == (int)HttpStatusCode.BadRequest &&
context.Response.Headers.ContainsKey(BearerConstants.CustomUnauthorizedHeaderKey))
{
context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
string headerValue = context.Response.Headers.Get(BearerConstants.CustomUnauthorizedHeaderKey);
context.Response.Headers.Remove(BearerConstants.CustomUnauthorizedHeaderKey);
ErrorMessage errorMessage = new ErrorMessage(headerValue);
string json = JsonConvert.SerializeObject(errorMessage, Formatting.Indented);
var customResponseBody = new StringContent(json);
var customResponseStream = await customResponseBody.ReadAsStreamAsync();
await customResponseStream.CopyToAsync(owinResponseStream);
owinResponse.ContentType = "application/json";
owinResponse.ContentLength = customResponseStream.Length;
owinResponse.Body = owinResponseStream;
}
}
}
When ErrorMessage is serialized to JSON returns an array of errors:
{
"errors":
[
"message": "the error message"
]
}
I set the BearerConstants.CustomUnauthorizedHeaderKey header in TokenAuthorizationServerProvider.ValidateClientAuthentication() method using a extension method:
public static void Rejected(this OAuthValidateClientAuthenticationContext context, string message)
{
Debug.WriteLine($"\t\t{message}");
context.SetError(message);
context.Response.Headers.Add(BearerConstants.CustomUnauthorizedHeaderKey, new string[] { message });
context.Rejected();
}
This is how TokenAuthorizationServerProvider is implemented:
public class TokenAuthorizationServerProvider : OAuthAuthorizationServerProvider
{
public override Task AuthorizeEndpoint(OAuthAuthorizeEndpointContext context)
{
// Only for breakpoint. Never stops.
return base.AuthorizeEndpoint(context);
}
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
// Check if grant_type is authorization_code
string grantType = context.Parameters[BearerConstants.GrantTypeKey];
if (string.IsNullOrEmpty(grantType) || grantType != BearerConstants.GrantTypeAuthorizationCode)
{
context.Rejected("Invalid grant type"); // Sets header for custom response
return;
}
// Check if client_id and client_secret are in the request
string clientId = context.Parameters[BearerConstants.ClientIdKey];
string clientSecret = context.Parameters[BearerConstants.ClientSecretKey];
if (string.IsNullOrEmpty(clientId) || string.IsNullOrEmpty(clientSecret))
{
context.Rejected("Client credentials missing"); // Sets header for custom response
return;
}
//Check if client_id and client_secret are valid
ApiClient client = await (new ApiClientService()).ValidateClient(clientId, clientSecret);
if (client != null)
{
// Client has been verified.
Debug.WriteLine($"\t\tClient has been verified");
context.OwinContext.Set<ApiClient>("oauth:client", client);
context.Validated(clientId);
}
else
{
// Client could not be validated.
context.Rejected("Invalid client"); // Sets header for custom response
}
}
public override async Task GrantAuthorizationCode(OAuthGrantAuthorizationCodeContext context)
{
TokenRequestParameters parameters = await context.Request.GetBodyParameters();
using (IUserService userService = new UserService())
{
ApplicationUser user = await userService.ValidateUser(parameters.Code);
if (user == null)
{
context.Rejected("Invalid code");
return;
}
// Initialization.
var claims = new List<Claim>();
// Setting
claims.Add(new Claim(ClaimTypes.Name, user.UserName));
// Setting Claim Identities for OAUTH 2 protocol.
ClaimsIdentity oAuthClaimIdentity = new ClaimsIdentity(claims, OAuthDefaults.AuthenticationType);
ClaimsIdentity cookiesClaimIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationType);
// Setting user authentication.
IDictionary<string, string> data = new Dictionary<string, string>{ { "userName", user.UserName } };
AuthenticationProperties properties = new AuthenticationProperties(data);
AuthenticationTicket ticket = new AuthenticationTicket(oAuthClaimIdentity, properties);
// Grant access to authorize user.
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(cookiesClaimIdentity);
}
}
}
ApiClientService.ValidateClient() checks on database that cliend ID and Secret are correct.
GrantAuthorizationCode() is based on the step 8 from ASP.NET MVC - OAuth 2.0 REST Web API Authorization Using Database First Approach tutorial. But this tutorial for grant_type = password and I think that something is wrong in here.
And the AuthorizationCodeProvider class:
public class AuthorizationCodeProvider : AuthenticationTokenProvider
{
public override void Create(AuthenticationTokenCreateContext context)
{
AuthenticationTicket ticket = context.Ticket;
string serializedTicket = context.SerializeTicket();
context.SetToken(serializedTicket);
}
public override void Receive(AuthenticationTokenReceiveContext context)
{
context.DeserializeTicket(context.Token);
// At this point context.Ticket.Identity.IsAuthenticated is true
}
}
I call to create method from the AuthorizationController that shows the Allow/Deny view. It is decorated with System.Web.Mvc.Authorize attribute, so if the user isn't authenticated he or she has to login using the default login page from MVC template project (/account/login):
[Authorize]
public class AuthorizationController : Controller
{
private const string ServiceScope = "service-name";
[HttpGet]
public async Task<ActionResult> Index(string client_id, string response_type, string redirect_uri, string scope, string state)
{
AuthorizationViewModel vm = new AuthorizationViewModel()
{
ClientId = client_id,
RedirectUri = redirect_uri,
Scope = scope,
State = state
};
if (scope == ServiceScope)
{
var authentication = HttpContext.GetOwinContext().Authentication;
authentication.SignIn(
new AuthenticationProperties { IsPersistent = true, RedirectUri = redirect_uri },
new ClaimsIdentity(new[] { new Claim(ClaimsIdentity.DefaultNameClaimType, User.Identity.Name) },
"Bearer"));
}
return View(vm);
}
[HttpPost]
[ValidateAntiForgeryToken]
[MultiButton(MatchFormKey = "authorization", MatchFormValue = "Allow")]
public async Task<ActionResult> Allow(AuthorizationViewModel vm)
{
if (ModelState.IsValid)
{
string code = await this.SetAuthorizationCode(vm.ClientId, vm.RedirectUri);
if (vm.Scope == ServiceScope)
{
string url = $"{vm.RedirectUri}?code={code}&state={vm.State}";
return Redirect(url);
}
else
{
return Redirect(vm.RedirectUri);
}
}
return View(vm);
}
[HttpPost]
[ValidateAntiForgeryToken]
[MultiButton(MatchFormKey = "authorization", MatchFormValue = "Deny")]
public async Task<ActionResult> Deny(AuthorizationViewModel vm)
{
// Removed for brevity
return View(vm);
}
private async Task<string> SetAuthorizationCode(string clientId, string redirectUri)
{
string userId = User.Identity.GetUserId();
ClaimsIdentity identity = new ClaimsIdentity(new GenericIdentity(clientId, OAuthDefaults.AuthenticationType));
AuthenticationTokenCreateContext authorizeCodeContext = new AuthenticationTokenCreateContext(
HttpContext.GetOwinContext(),
Startup.OAuthOptions.AuthorizationCodeFormat,
new AuthenticationTicket(
identity,
new AuthenticationProperties(new Dictionary<string, string>
{
{ "user_id", userId },
{ "client_id", clientId },
{ "redirect_uri", redirectUri }
})
{
IssuedUtc = DateTimeOffset.UtcNow,
ExpiresUtc = DateTimeOffset.UtcNow.Add(Startup.OAuthOptions.AuthorizationCodeExpireTimeSpan)
}));
Startup.OAuthOptions.AuthorizationCodeProvider.Create(authorizeCodeContext);
string code = authorizeCodeContext.Token;
IUserService userService = new UserService();
await userService.SetAuthorization(userId, true, code); // save to database
userService.Dispose();
return code;
}
}
The authorization code is created in SetAuthorizationCode() method, which is called in Allow() action. This SetAuthorizationCode() method code is based on this answer.
Questions
I now that is very long with a lot of code, but I'm stuck for some days and I didn't find the solution. I don't know the complete flow of the authorization, I think that I'm missing something.
What happens when I call /api/token? I mean, what are the steps in this part of the authentication/authorization flow?
What happens after AuthorizationCodeProvider.GrantAuthorizationCode()?
Why a cookie returned instead of token in the body?
I found the solution of the problem, it was the AuthenticationMiddleware. Once the body of the response is read, it remains empty and does not reach the client. So you have to rewrite the response body.
public class AuthenticationMiddleware : OwinMiddleware
{
public AuthenticationMiddleware(OwinMiddleware next) : base(next) { }
public override async Task Invoke(IOwinContext context)
{
var owinResponse = context.Response;
var owinResponseStream = owinResponse.Body;
var responseBuffer = new MemoryStream();
owinResponse.Body = responseBuffer;
await Next.Invoke(context);
if (context.Response.StatusCode == (int)HttpStatusCode.BadRequest &&
context.Response.Headers.ContainsKey(BearerConstants.CustomUnauthorizedHeaderKey))
{
// Customize the response
}
else
{
// Set body again with the same content
string body = Encoding.UTF8.GetString(responseBuffer.ToArray());
StringContent customResponseBody = new StringContent(body);
Stream customResponseStream = await customResponseBody.ReadAsStreamAsync();
await customResponseStream.CopyToAsync(owinResponseStream);
}
}
}

How do you prevent this kind of Exploit in Bearer Token in ASP .NET WebAPI?

The server sends me token like this:
K_ZOMie5a2jZZ40UgwabpF9bvhrfDg4QNB4oiYcf-29J0cowj-pAuA4pSaEJzS5qo_yjXlpui8d65y-nVQXCHHbmu9H7CcHUOUHJzaE1dORjDBQyhNB5-4aZvBaoFj6258x1RTz2F-FoL1ZwFHBevTmz_TdG2EY2XdjOySbDeV7XlDQly_ruO_LqUbs3Of1Vxj4tEXrSKCdomObk7eu_5T1srwp2_5uL6broAjcWLtykS0hgaUi5JRMxkx0tHcAjGxu1p5O3BYCllHqWH0ver4T0-rPI0T_s3Yq0qZT3g41BM4frtf8oJ-KQYHFlqxp1fGfVi1vgYxLMTD9z-jNlJI9-EmBhd3raZH-ASkiLKPw_NV5U9hbpz_1Fym3XQVf2ll7NeUa8R5E-pE9dn-qNsllbQ3Dhpg6J3cFzNBbssZt0y_qId4gFIHaeaqIWZY19XHACcwjX971lgLTY2mIwqDaSuOqygiu4tAZChp8Syo_HuYoJBjsNY-Wrys8Il8glJixI-_FCQOUors3yApZrrA
I have stored this token in a javascript variable on my web app which can be easily accessed from browser console if inspected and the webapp can access the api.
However, If I copy this token and paste in on postman or any other client , I will then be able access the protected resource.
How do I prevent this exploit? I want to somehow validate the token original client before requesting api
My ConfigureOAuth code:
public void ConfigureOAuth(IAppBuilder app)
{
OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = new SimpleAuthorizationServerProvider()
};
// Token Generation
app.UseOAuthAuthorizationServer(OAuthServerOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}
My OAuthAutorizationProviderCode:
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
string clientId = string.Empty;
string clientSecret = string.Empty;
if (!context.TryGetBasicCredentials(out clientId, out clientSecret))
{
context.TryGetFormCredentials(out clientId, out clientSecret);
}
if (context.ClientId == null)
{
context.SetError("invalid_client", "Client credentials could not be retrieved through the Authorization header.");
context.Rejected();
return;
}
try
{
// You're going to check the client's credentials on a database.
if (clientId == "MyApp" && clientSecret == "MySecret")
{
ApplicationClient client = new ApplicationClient();
client.Id = clientId;
client.AllowedGrant = OAuthGrant.ResourceOwner;
client.ClientSecretHash = new PasswordHasher().HashPassword("MySecret");
client.Name = "My App";
client.CreatedOn = DateTimeOffset.UtcNow;
context.OwinContext.Set<ApplicationClient>("oauth:client", client);
context.Validated(clientId);
}
else
{
// Client could not be validated.
context.SetError("invalid_client", "Client credentials are invalid.");
context.Rejected();
}
}
catch (Exception ex)
{
string errorMessage = ex.Message;
context.SetError("server_error");
context.Rejected();
}
return;
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
ApplicationClient client = context.OwinContext.Get<ApplicationClient>("oauth:client");
if (client.AllowedGrant != OAuthGrant.ResourceOwner)
{
context.SetError("invalid_grant", "The resource owner credentials are invalid or resource owner does not exist.");
context.Rejected();
return;
}
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
SpLoginTsk_Result model = new SpLoginTsk_Result();
model = this.acs.Login(context.UserName, context.Password);
if (model==null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
var identity = new ClaimsIdentity(new[]
{ new System.Security.Claims.Claim(ClaimTypes.Name, model.PersonId.ToString()),
new System.Security.Claims.Claim(ClaimTypes.Role,model.RoleId.ToString())
}, "ApplicationCookie", ClaimTypes.Name, ClaimTypes.Role
);
AuthenticationProperties properties = CreateAuthenticationProperties(model);//Dictionary used to store state values about the authentication session.
AuthenticationTicket ticket = new AuthenticationTicket(identity, properties);//Contains user identity information as well as additional authentication state.
context.Validated(ticket);//Replaces the ticket information on this context and marks it as as validated by the application. IsValidated becomes true and HasError becomes false as a result of calling
context.Request.Context.Authentication.SignIn(identity);
}
public override Task TokenEndpoint(OAuthTokenEndpointContext context)
{
foreach (KeyValuePair<string, string> property in context.Properties.Dictionary)
{
context.AdditionalResponseParameters.Add(property.Key, property.Value);
}
return Task.FromResult<object>(null);
}

Owin OAuth Context: Frontend login sending Json

I have a Web API project using OAuth/Owin to deal with authentication.
Everything is working fine when i post through form-urlencoded. But the frontend team will post application/json, and i was not able to change my method to receive this Json.
I usually use [FromBody] when i want to recieve a Json, but it didn't work this time.
My code:
public override async Task ValidateClientAuthentication([FromBody]OAuthValidateClientAuthenticationContext context)
{
context.Validated();
}
public override async Task GrantResourceOwnerCredentials([FromBody]OAuthGrantResourceOwnerCredentialsContext context)
{
try
{
....
}
catch (Exception e)
{
context.SetError("invalid_grant", "User not found");
}
}
}
My OAuth Config:
public static void ConfigureOAuth(IAppBuilder app, IContainer container)
{
OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = true, // HTTPS == false
TokenEndpointPath = new PathString("/security/login"),
AccessTokenExpireTimeSpan = TimeSpan.FromHours(2),
Provider = container.Resolve<IOAuthAuthorizationServerProvider>()
};
app.UseOAuthAuthorizationServer(OAuthServerOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}
Json Example:
{grant_type: "password", username : "myuser", password: "mypass"}
Read in the request body by
context.Request.Body.Position = 0; // this resets the read position to 0
var payload = await new StreamReader(context.Request.Body).ReadToEndAsync();
From here you have the string of your JSON object. You can use a deserializer to convert it to a CLR type.

Retrieve access token from a customised header

In my Web API, I want to get the access token from the Cookies header in the request, and then do the validation on the token. At the moment, IdentityServer3.AccessTokenValidation package is used to do the validation of the Bearer token and it looks for the token from the Authorization header only. Preferably I would like to keep using the same bearer token validation process, but getting the token from the Cookies header, does that sound doable with handy codes? Thanks
Just implement your own TokenProvider and provide it to the AccessTokenValidationMiddleware:
public class MyCustomTokenProvider : IOAuthBearerAuthenticationProvider
{
public Task RequestToken(OAuthRequestTokenContext context)
{
if (context.Token == null)
{
//try get from cookie
var tokenCookie = context.Request.Cookies["myCookieName"];
if (tokenCookie != null)
{
context.Token = tokenCookie;
}
}
return Task.FromResult(0);
}
public Task ValidateIdentity(OAuthValidateIdentityContext context)
{
throw new NotImplementedException();
}
public Task ApplyChallenge(OAuthChallengeContext context)
{
throw new NotImplementedException();
}
}
In your Startup.cs:
app.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions
{
Authority = "http://myhost",
RequiredScopes = new[] { "my-scope" },
TokenProvider = new MyCustomTokenProvider()
});

Use Active Directory with Web API for SPA

I am building single page application and I would like to know user's identity. We have Active Directory in our intranet but I don't know much about it. I am able to use code like this to verify username and password.
using(PrincipalContext pc = new PrincipalContext(ContextType.Domain, "YOURDOMAIN"))
{
bool isValid = pc.ValidateCredentials("myuser", "mypassword");
}
In fact that is everything I need from the Active Directory side of thing. So I could create some AuthorizationFilter with this code but this would mean that username and password must be in every request. I would like to send username and password only once and then use some token for authorization. So there should be some token provider inside my server-side application.
I am building this application from scratch so I can use the latest .net technologies. I found that there are some Owin middlewares which are handling tokens, cookies and OAuth. Could anything of that help me?
When I was searching for something completely different, I found this incredible project on CodePlex: https://angularapp.codeplex.com/ It is exactly what I was looking for.
Update:
The part of this app which was useful to me is the DomainUserLoginProvider
public class DomanUserLoginProvider : ILoginProvider
{
public bool ValidateCredentials(string userName, string password, out ClaimsIdentity identity)
{
using (var pc = new PrincipalContext(ContextType.Domain, _domain))
{
bool isValid = pc.ValidateCredentials(userName, password);
if (isValid)
{
identity = new ClaimsIdentity(Startup.OAuthOptions.AuthenticationType);
identity.AddClaim(new Claim(ClaimTypes.Name, userName));
}
else
{
identity = null;
}
return isValid;
}
}
public DomanUserLoginProvider(string domain)
{
_domain = domain;
}
private readonly string _domain;
}
LoginProvider is used in AccountController's action to verify the credentials. Also the AccessToken is created here.
[HttpPost, Route("Token")]
public IHttpActionResult Token(LoginViewModel login)
{
ClaimsIdentity identity;
if (!_loginProvider.ValidateCredentials(login.UserName, login.Password, out identity))
{
return BadRequest("Incorrect user or password");
}
var ticket = new AuthenticationTicket(identity, new AuthenticationProperties());
var currentUtc = new SystemClock().UtcNow;
ticket.Properties.IssuedUtc = currentUtc;
ticket.Properties.ExpiresUtc = currentUtc.Add(TimeSpan.FromMinutes(30));
return Ok(new LoginAccessViewModel
{
UserName = login.UserName,
AccessToken = Startup.OAuthOptions.AccessTokenFormat.Protect(ticket)
});
}
Last but not least, add the correct middleware to Owin pipeline.
public partial class Startup
{
static Startup()
{
OAuthOptions = new OAuthAuthorizationServerOptions();
}
public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }
public static void ConfigureAuth(IAppBuilder app)
{
app.UseOAuthBearerTokens(OAuthOptions);
}
}
On the client side you just send the request with credentials to the Token action of AccountController and you'll get the authentication token. Save this token in browser ("Remember me" feature) and set the Angular's $http service to attach token to each request.
services.factory('$auth', ['$q', '$http', '$path', function ($q, $http, $path) {
var tokenUrl = $path('api/Account/Token');
function setupAuth(accessToken, remember) {
var header = 'Bearer ' + accessToken;
delete $http.defaults.headers.common['Authorization'];
$http.defaults.headers.common['Authorization'] = header;
sessionStorage['accessToken'] = accessToken;
if (remember) {
localStorage['accessToken'] = accessToken;
}
return header;
}
var self = {};
self.login = function(user, passw, rememberMe) {
var deferred = $q.defer();
$http.post(tokenUrl, { userName: user, password: passw })
.success(function (data) {
var header = setupAuth(data.accessToken, rememberMe);
deferred.resolve({
userName: data.userName,
Authorization: header
});
})
.error(function() {
deferred.reject();
});
return deferred.promise;
};
return self;
}]);
Surely the OAuth is a way to go but why don't you consider old good Forms Authentication? It fits the scenario perfectly - with a custom provider you can authenticate users in the AD and then all consecutive client requests carry the authentication cookie which can be used to authenticate server calls.
The Forms cookie in this scenario plays the role of a "token" you think of.

Categories

Resources