I successfully implemented my custom OAuthAuthorizationServerProvider. But when I log in and retrieve a token, my client doesn't have any idea of the user's roles, claims, etc.
I currently added a webapi controller to return the list of the principal's claims, but I'm not really happy with that.
When requesting a token, the current response looks like:
{
access_token: "qefelgrebjhzefilrgo4583535",
token_type: "bearer",
expires_in: 59
}
Q> How can make it return something like the following snippet?
{
access_token: "qefelgrebjhzefilrgo4583535",
token_type: "bearer",
expires_in: 59,
user: {
name: 'foo',
role: 'bar'
}
}
My progress so far:
The documentation of OAuthAuthorizationServerProvider#TokenEndpoint(OAuthTokenEndpointContext) says:
Called at the final stage of a successful Token endpoint request. An application
may implement this call in order to do any final modification of the claims
being used to issue access or refresh tokens. This call may also be used
in order to add additional response parameters to the Token endpoint's json
response body.
I couldn't find any example of how to customize the response, and asp-net Identity's source code is not yet released, so I'm quite stuck.
May be you are looking for TokenEndpoint method override of OAuthAuthorizationServerProvider.
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);
}
I believe you need to override TokenEndpointResponse on OAuthAuthorizationServerProvider class :
public override Task TokenEndpointResponse(OAuthTokenEndpointResponseContext context)
{
context.AdditionalResponseParameters.Add("Key","Value");
return base.TokenEndpointResponse(context);
}
Related
I have set up authentication in a Spring WebFlux application. The authentication mechanism appears to work fine. For example the following code is used to set up security web filter chain:
#Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
return http.authorizeExchange()
.pathMatchers("/path/to/resource").hasAuthority("A_ROLE")
.anyExchange().authenticated()
.and().httpBasic()
.and().build();
}
This works as expected in conjunction with the UserDetailsRepositoryReactiveAuthenticationManager and MapReactiveUserDetailsService. If a user doesn't have the required authority a forbidden error code is returned and otherwise the request is passed on to the handler.
I have a requirement to apply fine grained permission checks within the handler itself and figured that I should be able to retrieve the authorities from the request as follows:
public Mono<ServerResponse> getMyResource(ServerRequest serverRequest) {
Authentication authentication = (Authentication)serverRequest.principal().block();
...
}
However, I find that the principal is always null. First, is this the correct way to get a handle on the authorities, and if so is there possibly some upstream configuration I'm missing?
You are blocking the result before is available. You can simply flatmap it so that you don't have to block it.
public Mono<ServerResponse> getMyResource(ServerRequest serverRequest) {
return serverRequest.principal().flatMap((principal) -> ServerResponse.ok()
.body(fromObject("Hello " + principal.getName())));
}
UPDATE: If you want to retrieve the principal and body you could zip them.
public Mono<ServerResponse> getMyResource(ServerRequest serverRequest) {
return Mono.zip(
serverRequest.principal(),
serverRequest.bodyToMono(String.class)
).flatMap(tuple -> {
Principal principal = tuple.getT1();
String body = tuple.getT2();
return ServerResponse.ok().build();
});
}
I am using a 3rd party library names telogis map in my project. For one of its functionality called Clustering, it is not possible to send request header. Only query string can be passed for clustering and entire logic of API call is done within the JS library.
My project use Bearer token based authenticate and built with Web API 2. To resolve this issue I have passed access token in query string and want validate the request. I created below CustomAuthorize attribute for this:
public class ClusterRequestAuthorizeAttribute : AuthorizeAttribute
{
public override void OnAuthorization(HttpActionContext actionContext)
{
base.OnAuthorization(actionContext);
}
public override Task OnAuthorizationAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
{
string accessToken = actionContext.Request.GetQueryNameValuePairs().Where(w => w.Key == "access_token").Select(w => w.Value).DefaultIfEmpty().FirstOrDefault();
actionContext.Request.Headers.Remove("Authorization");
actionContext.Request.Headers.Add("Authorization", accessToken);
actionContext.ControllerContext.Request.Headers.Remove("Authorization");
actionContext.ControllerContext.Request.Headers.Add("Authorization", accessToken);
HttpContext.Current.Request.Headers.Remove("Authorization");
HttpContext.Current.Request.Headers.Add("Authorization", accessToken);
return base.OnAuthorizationAsync(actionContext, cancellationToken);
}
protected override bool IsAuthorized(HttpActionContext actionContext)
{
return base.IsAuthorized(actionContext);
}
}
But IsAuthorized is always returning false. I reviewed the Authorize API internal function using Git Link
According to it, I have to set actionContext.ControllerContext.RequestContext.Header which is not accessible due to protection level as it is marked as internal.
Is there any other work around for this issue or can it be done in better way?
As a test, I created a fresh Asp.Net MVC5 app using the latest template in Visual Studio 2013. I added the following method to Global.asax.cs:
protected void Application_PreSendRequestHeaders()
{
Response.AppendCookie(new HttpCookie("TotalNumberOfCookiesInApplication_EndRequestIs", Response.Cookies.Count + string.Empty));
}
When I start the app and do a POST to /Account/Login using the credentials of a registered user, the cookies that get returned to the client are:
Note that the custom cookie I've added shows that there are no cookies set in the response by the time Application_PreSendRequestHeaders() is called. Despite this, all the Auth cookies arrive at the client. I was of the understanding that Application_PreSendRequestHeaders() is the last stage we can "hook" into for modifying cookies. Is the Owin middleware able to somehow add cookies after that, or am I missing something?
(In case you're interested, my motivation for all this is: I'm trying to modify the domain of the auth cookies to be ".abc.com", where "abc.com" is the last two parts of the host in the request URI. I want to do this to support authentication across multiple subdomains. Setting the CookieDomain in the context of the global Owin configuration (IAppBuilder) isn't enough because the request host changes between our debug/staging/production environments, and we often deploy the production code to Azure staging first for testing before doing a VIP swap).
(Note also that I'm aware of posts like this one, however it doesn't explain where the cookies are actually set)
EDIT:
Based on a bit more searching, it seems I'm looking into the wrong pipeline. Owin has its own pipeline, so I found this post which describes how we can hook into it. Viola...there were the cookies. Would be great if anybody could confirm that this is indeed the most sensible way to do it.
EDIT 2:
Finally decided to look into the Katana source code and found out that all I needed to do to get my cookie domains set was the following code in my CookieAuthenticationProvider
OnResponseSignIn = context =>
{
// Example only!
context.CookieOptions.Domain = context.Request.Uri.Host;
},
OnResponseSignOut = context =>
{
// Example only!
context.CookieOptions.Domain = context.Request.Uri.Host;
}
Edit 3:
An even cleaner solution for my case was just to use a custom cookie manager, which set the cookie domain based on the current request URI:
/// <summary>
/// This class simply appends the cookie domain to the usual auth cookies
/// </summary>
public class ChunkingCookieManagerWithSubdomains : ICookieManager
{
private readonly ChunkingCookieManager _chunkingCookieManager;
public ChunkingCookieManagerWithSubdomains()
{
_chunkingCookieManager = new ChunkingCookieManager();
}
public string GetRequestCookie(IOwinContext context, string key)
{
return _chunkingCookieManager.GetRequestCookie(context, key);
}
public void AppendResponseCookie(IOwinContext context, string key, string value, CookieOptions options)
{
// Simplification (use the context parameter to get the required request info)
options.Domain = ".domainBasedOnRequestInContext.com";
_chunkingCookieManager.AppendResponseCookie(context, key, value, options);
}
public void DeleteCookie(IOwinContext context, string key, CookieOptions options)
{
// Simplification (use the context parameter to get the required request info)
options.Domain = ".domainBasedOnRequestInContext.com";
_chunkingCookieManager.DeleteCookie(context, key, options);
}
}
...which is then set in the Cookie Auth Options in the Owin setup:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
...
CookieManager = new ChunkingCookieManagerWithSubdomains(),
...
}
});
Hope that helps somebody coming across the same kind of question.
As requested by Tieson, here's a summary of my edits in the original post above, as an answer.
Suggested solution: Use a custom cookie manager.
/// <summary>
/// This class simply appends the cookie domain to the usual auth cookies
/// </summary>
public class ChunkingCookieManagerWithSubdomains : ICookieManager
{
private readonly ChunkingCookieManager _chunkingCookieManager;
public ChunkingCookieManagerWithSubdomains()
{
_chunkingCookieManager = new ChunkingCookieManager();
}
public string GetRequestCookie(IOwinContext context, string key)
{
return _chunkingCookieManager.GetRequestCookie(context, key);
}
public void AppendResponseCookie(IOwinContext context, string key, string value, CookieOptions options)
{
// Simplification (use the context parameter to get the required request info)
options.Domain = ".domainBasedOnRequestInContext.com";
_chunkingCookieManager.AppendResponseCookie(context, key, value, options);
}
public void DeleteCookie(IOwinContext context, string key, CookieOptions options)
{
// Simplification (use the context parameter to get the required request info)
options.Domain = ".domainBasedOnRequestInContext.com";
_chunkingCookieManager.DeleteCookie(context, key, options);
}
}
...which can then be set in the Cookie Auth Options in the Owin setup:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
...
CookieManager = new ChunkingCookieManagerWithSubdomains(),
...
}
});
I create a custom BasicAuthenticationMiddleware that use a BasicAuthenticationHandler to Authenticate requests from client to WebAPI.
The BasicAuthenticationHandler derives from the AuthenticationHandler< TOptions > base class.
Everything works fine and I implemented the
AuthenticateCoreAsync
where the logic to authenticate happens
ApplyChallengeResponseAsync
where the logic, in case of not authenticated requests, sends the WWW-Authenticate header to the client.
What I would like to achieve now is to set a Custom Body in the Response (IOwinResponse, inside the ApplyChallengeResponseAsync, with a custom object like:
{
Code="999",
Description="My failing reason"
AdditionalInfo = "My additional infos"
}
instead of the standard message that is like
{
message="Authorization has been denied for this request."
}
Did you have any suggestion on this?
thanks
The standard message you see, which is "Authorization has been denied for this request." is created by the Authorize filter. The HandleUnauthorizedRequest method sets this message in the response.
protected virtual void HandleUnauthorizedRequest(HttpActionContext actionContext)
{
if (actionContext == null)
{
throw Error.ArgumentNull("actionContext");
}
actionContext.Response = actionContext.ControllerContext.Request
.CreateErrorResponse(
HttpStatusCode.Unauthorized,
SRResources.RequestNotAuthorized);
}
SRResources.RequestNotAuthorized is what you see as the standard message.
Now, ApplyChallengeResponseAsync is called from the OnSendingHeaders callback in Katana autentication micro framework. This callback is invoked when a component writes into the response stream. In our case, when the response message created by the filter (what you see above) gets serialized, that is when the callback is invoked and ApplyChallengeResponseAsync runs. By that time, it is already too late for you to change the response. The best bet will be to override the virtual method of the Authorize filter above like this.
public class MyAuthorizeAttribute : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(HttpActionContext actionContext)
{
var response = actionContext.Request.CreateResponse<MyError>
(new MyError() { Description = "My failing reason" });
response.StatusCode = HttpStatusCode.Unauthorized;
actionContext.Response = response;
}
}
public class MyError
{
public string Description { get; set; }
}
Instead of using [Authorize] on the controller or action method, use [MyAuthorize].
[This question relates to ASP.NET MVC4, and it is about best-practice approach - so please, don't suggest hacks.]
I want to authenticate users using an auth token sent in the request URL. It works similarly to a password reset token, except in this case it does not go to a reset page but instead grants access to some portion of the site. The idea is to send the URL with the auth token to a verified email address of the user. Users can click the link and perform some actions without typing their password.
Out-of-the-box, ASP.NET has the [Authorize] attribute and the SimpleMembershipProvider - these seem to work great, but they do some voodoo magic under the hood (like auto-generating database tables), so I don't know how to extend them to add this link-based auth token.
I don't expect an exact answer, but please do point me to the right direction.
Thanks!
Uf, broad question. But I will try at least to direct you to a right direction.
So first if suggest that you use Forms Authentication as a base, but you will have to customize using of it. And I presume that you do not want to use cookies for the authentication as this is native behaviour of the Forms Authentication.
The most important point you should consider to have it you custom query string token based authentication.
Create a login action and in this action you will authorize the user, if he have granted access you ask FormsAuthentication to create AuthCookie. For the further on you just take the httpCookie.Value as your auth token that you will carry in query string.
You need to implement the Application_BeginRequest in the Global.asax that will handle this query string tokens and translate it into the cookie. With this approach you can leverage all the ASP.NET Forms Authentication infrastructure.
This is quite high level picture w/o code. If you need more detail help I can also provide it to you.
You should just use a regular Action that accepts HttpGet.
Upon receiving the token, immediately invalid it so it can't be used again.
Also, only accept tokens that are within your pre-defined range of time period, like 24 or 72 hours.
Thank you Peter for idea.
If smb need to create JWT token authorization for old ASP.NET MVC5.I wrote small example. I don't serialize cookie to JWT. I create a JWT and after I am checking it in the BeginRequest. If everything is ok, I create a cookie and set it to the httpContext.Request. I used authentication mode="Forms" for application and it require cookies.
For create JWT token:
const string secret = "GQDstcKsx0NHjPOuXOYg5MbeJ1XT0uFiwDVvVBrk";
[AllowAnonymous]
[HttpPost]
public ActionResult LoginJWT(LoginViewModel model)
{
ActionResult response = null;
if (ModelState.IsValid)
{
if (true) //todo: check user login&password
{
var payload = new Dictionary<string, object>
{
{ "iss", "subject" },
{ "sub", "api" },
{ "exp", DateTimeOffset.UtcNow.AddHours(2).ToUnixTimeSeconds()},
{ "iat", DateTimeOffset.UtcNow.ToUnixTimeSeconds()},
{ "jti", Guid.NewGuid() },
{ "uid", "64" } //custom field for identificate user
};
IJwtAlgorithm algorithm = new HMACSHA256Algorithm(); // symmetric
IJsonSerializer serializer = new JsonNetSerializer();
IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlEncoder);
var token = encoder.Encode(payload, secret);
response = Content(token);
}
else
{
response = new HttpStatusCodeResult(System.Net.HttpStatusCode.BadRequest, "Login or password are not found");
}
}
else
{
response = new HttpStatusCodeResult(System.Net.HttpStatusCode.BadRequest, "Errors in Model");
}
return response;
}
For check JWT token in Global.asax:
public override void Init()
{
this.BeginRequest += this.BeginRequestHandler;
base.Init();
}
private void BeginRequestHandler(object sender, EventArgs e)
{
var bearerToken = this.Context.Request.Headers["Authorization"];
if (bearerToken != null)
{
var token = bearerToken.StartsWith("Bearer ") ? bearerToken.Substring(7) : bearerToken;
const string secret = "GQDstcKsx0NHjPOuXOYg5MbeJ1XT0uFiwDVvVBrk";
int userId = 0;
try
{
IJsonSerializer serializer = new JsonNetSerializer();
var provider = new UtcDateTimeProvider();
IJwtValidator validator = new JwtValidator(serializer, provider);
IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
IJwtAlgorithm algorithm = new HMACSHA256Algorithm(); // symmetric
IJwtDecoder decoder = new JwtDecoder(serializer, validator, urlEncoder, algorithm);
var json = decoder.DecodeToObject<IDictionary<string, string>>(token, secret, verify: true);
if (json.TryGetValue("uid", out var uid))
{
userId = Convert.ToInt32(uid);
}
}
catch (TokenExpiredException)
{
Console.WriteLine("Token has expired");
}
catch (SignatureVerificationException)
{
Console.WriteLine("Token has invalid signature");
}
if (userId != 0)
{
// check user by id, if found create cookie.
}
}
}
I used:
jwt-dotnet/jwt library 7.2.1