When adding a client registration in OpenIddict (via OpenIddictClientRegistration), the registration class has a Scopes property with the description:
Gets the list of scopes sent by default as part of authorization requests.
I am attempting to use this for any scopes I want to be on the token by default:
options.AddRegistration(new OpenIddictClientRegistration
{
// ...
Scopes =
{
"scope1",
//...
"scopeN",
},
// ...
});
However when the OpenIddictClientService goes on to call AuthenticateWithClientCredentialsAsync it does not seem to set these scopes on the ProcessAuthenticationContext it creates, so will only ever use the list of scopes passed directly to the AuthenticateWithClientCredentialsAsync method.
The registration itself is passed into the context, but this does not seem to have any bearing on the scopes used.
Am I misunderstanding how the Scopes property on the OpenIddictClientRegistration should work or is there something else I need to set in order for these scopes to be passed by default with any request?
Related
I am not using Identity.
I have this ASP.NET Core configuration enabling two authentication schemes, cookies and basic auth:
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.Cookie.Name = "_auth";
options.Cookie.HttpOnly = true;
options.LoginPath = new PathString("/Account/Login");
options.LogoutPath = new PathString("/Account/LogOff");
options.AccessDeniedPath = new PathString("/Account/Login");
options.ExpireTimeSpan = TimeSpan.FromHours(4);
options.SlidingExpiration = true;
})
.AddScheme<AuthenticationSchemeOptions, BasicAuthenticationHandler>("BasicAuthentication", null);
BasicAuthenticationHandler is a custom class inheriting from AuthenticationHandler and overriding HandleAuthenticateAsync to check the request headers for basic authentication challenge, and returns either AuthenticateResult.Fail() or AuthenticateResult.Success() with a ticket and the user claims.
It works fine as is:
Controllers/Actions with the [Authorize] tag will check the cookies and redirect to the login page is not present.
Controllers/Actions with the [Authorize(AuthenticationSchemes = "BasicAuthentication")] tag will check the header and reply a 401 Unauthorized HTTP code if not present.
Controllers/Actions with the [Authorize(AuthenticationSchemes = "BasicAuthentication,Cookies")] tag will allow both methods to access the page, but somehow use the Cookies redirection mechanism when failing both checks.
My goal is to have most of my project to use Cookies (hence why it is set as default), but have some API type of controllers to accept both methods. It should also be possible to tag the Controllers/Actions to return a specific Json body when desired (as opposed to the login redirect or base 401 response), but only for certain controllers.
I've spent the last 2 days reading different similar questions and answers here on StackOverflow, nothing seems to accommodate my need.
Here's a few methods I found:
The options under AddCookie allow you to set certain events, like OnRedirectToAccessDenied and change the response from there. This does not work because it applies to the whole project.
Under my BasicAuthenticationHandler class, the AuthenticationHandler class allow to override HandleChallengeAsync to change the response from there instead of replying 401. Unfortunately, again it applies globally to everywhere you use the scheme, not on a controller/action level. Not sure if it's applied when mixing multiple schemes either.
Many answers point to adding a Middleware to the solution, again, it impacts the whole project.
Many answers point to Policies, but it seems to be to control whether or not an user have access to the resource based on claims, not controlling the response when he do not.
Many answers suggest creating a class inheriting from AuthorizeAttribute, IAuthorizationFilter. Again, this allow to override the OnAuthorization method to decide if the user have the right or not to access the resource, but not to control the response AFTER the normal authentication scheme failed.
I'm thinking either there's a filter type I'm missing, or maybe I need to create a third authentication type that will mix the previous two and control the response from there. Finding a way to add a custom error message in the options would also be nice.
I managed to do it via a IAuthorizationMiddlewareResultHandler. Not my favorite approach because there can be only one per project and it intercepts all calls, but by checking if a specific (empty) attribute is set, I can control the flow:
public class JsonAuthorizationAttribute : Attribute
{
public string Message { get; set; }
}
public class MyAuthorizationMiddlewareResultHandler : IAuthorizationMiddlewareResultHandler
{
private readonly AuthorizationMiddlewareResultHandler DefaultHandler = new AuthorizationMiddlewareResultHandler();
public async Task HandleAsync(RequestDelegate requestDelegate, HttpContext httpContext, AuthorizationPolicy authorizationPolicy, PolicyAuthorizationResult policyAuthorizationResult)
{
// if the authorization was forbidden and the resource had specific attribute, respond as json
if (policyAuthorizationResult.Forbidden)
{
var endpoint = httpContext.GetEndpoint();
var jsonHeader = endpoint?.Metadata?.GetMetadata<JsonAuthorizationAttribute>();
if (jsonHeader != null)
{
var message = "Invalid User Credentials";
if (!string.IsNullOrEmpty(jsonHeader.Message))
message = jsonHeader.Message;
httpContext.Response.StatusCode = 401;
httpContext.Response.ContentType = "application/json";
var jsonResponse = JsonSerializer.Serialize(new
{
error = message
});
await httpContext.Response.WriteAsync(jsonResponse);
return;
}
}
// Fallback to the default implementation.
await DefaultHandler.HandleAsync(requestDelegate, httpContext, authorizationPolicy, policyAuthorizationResult);
}
}
I was typing this on comment... but it's doesn't fit... so here is something we probably need to make clear before choosing a solution:
Authorization process happen at the upper middleware above controller
Yes, AuthorizationMiddleware was registered when we use app.UseAuthorization();, that quite far above controller layer, so it was returned long before the request can reach controller, so, any type of filter cannot be applied here.
Not specify an authentication scheme or policy would easily lead to un-stable behavior.
Imagine, Authentication process return an instance of User that stick with the request, but what would happen if the permission on cookie and basicAuth was difference, like cookie have myclaim, while basicAuth doens't ? Related process on both type of scheme was difference (like challenge on cookie would lead to /Account/Login and basicAuth to /Login ?). And various logic case that we could implement on each page.
I Know, this is not possible, but it would become a mess, not for the author of these code, but for those maintainers to come.
Json response for some specific process on client ?
This might sound detailed at first glance, but it would rather become burden soon, if some more authentication requirement raise after that (like Jwt). Covering each of these case on client would make user experience quite awkward (like, half-authentication and authorization).
And if It's un-avoidable in the project. Might I suggest create a default authentication scheme with ForwardDefaultSelector that would elected which authentication scheme to use for each request. And maintain a stable routing HashSet that would use to detect on which endpoint to set Json Response as wished on some upper level than AuthorizationMiddleware, by using middleware, ofcourse. Then, we narrow down to 2 centralize places to checkout the authorization.
Chaos came when we tried to make one thing to do somethings. At least in this case, I think we would breath easier when coming to debug phase.
I'm looking how to to implement the deprecated Controller::isAuthorized() with the new Authentication & Authorization plugins but I could not find the way.
For example in the method RequestPolicyInterface::canAccess() called by the RequestAuthorizationMiddleware I could not get an instance of the current Controller.
Any idea ??
Thanks.
Policies apply to, and receive resources, the request policy applies to requests, and will receive the current request object accordingly, additionally to the current identity which policies do always receive.
The specific signature for the canAccess method in this case is:
canAccess(\Authorization\IdentityInterface $identity, \Cake\Http\ServerRequest $request)
eg, the method will receive the current request in the second argument, and you can obtain the routing information from the request parameters::
public function canAccess($identity, ServerRequest $request)
{
if (
!$request->getParam('plugin') &&
!$request->getParam('prefix') &&
$request->getParam('controller') === 'Articles'
) {
return $identity->role->name === 'admin';
}
return true;
}
This would allow only users with the role admin to access to the (non-plugin/prefixed) Articles controller, and allow access to all other controller to anyone.
Note that you will not receive an instance of the controller, as that specific check is a) not made against a controller object but a request object, and b) happens before the controller is even instantiated.
See also
Authorization Cookbook > Request Authorization Middleware
Using ASP.NET Core with OpenIddict password grant.
When calling an authentication end point, I am getting this:
{
"token_type": "Bearer",
"access_token": "eyJhbGciOiJ...",
"expires_in": 1800
}
How can I include the user id in the response? I can see it in the decoded token, but my user app will not be decoding it.
How can I include the user id in the response?
Ideally, consider using the identity token - always a JWT by definition - returned by OpenIddict when you specify scope=openid.
Alternatively, you can also enable the userinfo endpoint and send a userinfo request to get back a sub claim containing the user identifier: http://openid.net/specs/openid-connect-core-1_0.html#UserInfo.
If you really prefer returning the user identifier as a token response property, you have two options:
Using a special "public" property (in your authorization controller, where authentication tickets are created):
ticket.SetProperty("user_id" + OpenIddictConstants.PropertyTypes.String, user.Id);
Note: OpenIddictConstants.PropertyTypes.String is a special suffix indicating the authentication property added to the ticket can be exposed as part of the token response. Other constants are available if you prefer returning your identifier as a JSON number or a more complex JSON structure.
Using the events model (in Startup.cs):
services.AddOpenIddict()
// Register the OpenIddict core services.
.AddCore(options =>
{
// ...
})
// Register the OpenIddict server handler.
.AddServer(options =>
{
// ...
options.AddEventHandler<OpenIddictServerEvents.ApplyTokenResponse>(
notification =>
{
if (string.IsNullOrEmpty(notification.Context.Error))
{
var principal = notification.Context.Ticket.Principal;
var response = notification.Context.Response;
response["user_id"] = principal.FindFirst(OpenIddictConstants.Claims.Subject).Value;
}
return Task.FromResult(OpenIddictServerEventState.Unhandled);
});
})
// Register the OpenIddict validation handler.
.AddValidation();
Trying to use ServiceStack for authentication, and have it re-direct to a login page as follows:
Plugins.Add(new AuthFeature(
() => new CustomUserSession(), //Use your own typed Custom UserSession type
new IAuthProvider[] {
new CredentialsAuthProvider(), //HTML Form post of UserName/Password credentials
new TwitterAuthProvider(appSettings), //Sign-in with Twitter
new FacebookAuthProvider(appSettings), //Sign-in with Facebook
new DigestAuthProvider(appSettings), //Sign-in with Digest Auth
new BasicAuthProvider(), //Sign-in with Basic Auth
new GoogleOpenIdOAuthProvider(appSettings), //Sign-in with Google OpenId
new YahooOpenIdOAuthProvider(appSettings), //Sign-in with Yahoo OpenId
new OpenIdOAuthProvider(appSettings), //Sign-in with Custom OpenId
}, "http://www.anyURIhereisignored.com"));
However the URI argument in this case "http://www.anyURIhereisignored.com" is simply ignored.
Looking at the class definition for AuthFeature, I see that the htmlRedirect param is declared as optional with a default value of "~/login", however it appears that it is always using that value and ignoring whatever was passed in. It seems that although the htmlRedirect value gets set initially to the passed URI, somehow internally it is never using that, instead always defaulting to "~/login". Is anyone else experiencing the same issue?
If you're using MVC4 and your controllers are inheriting from the ServiceStackController<> as per the ServiceStack doco, then you may want to try overriding the LoginRedirectUrl property:
public override string LoginRedirectUrl
{
get { return "/Account/Login?redirect={0}"; }
}
This will redirect any unauthenticated requests for secured actions to the login url composed from the specified value.
You should also make sure you remove the ASP.NET membership modules from the web.config if you want to use ServiceStack auth in MVC.
I have a custom URI set as "~/account" and it is working fine. I tried changing it to google and the results was as you are looking for:
https://www.google.co.uk/?redirect=http://localhost/secure
Have you overridden the redirect in the attribute that indicates authentication is required ie.
[Authenticate(ApplyTo.All,"~/login")]
?
How would I implement the following scenario using ServiceStack?
Initial request goes to http://localhost/auth having an Authorization header defined like this:
Authorization: Basic skdjflsdkfj=
The IAuthProvider implementation validates against a user store and returns a session token as a response body (JSON).
The client uses this token an resends it against the subsequent requests like http://localhost/json/reply/orders using the Authorization header like this:
Authorization: BasicToken <TokenFromPreviousSuccessfulLogin>
Using a AuthenticateAttribute I want to flag my Service to use Authentication.
How should I implement the validation of the token for the subsequent requests?
How should I implement the IAuthProvider to provide the token?
How would I register the Providers etc.? Using RequestFilters or using the AuthFeature?
ServiceStack's JWT AuthProvider and API Key AuthProvider both use token based Authentication.
Otherwise the BasicAuth Provider is a very simple class that simply extracts the UserName and Password from the BasicAuth Header and evaluates it:
public override object Authenticate(...)
{
var httpReq = authService.RequestContext.Get<IHttpRequest>();
var basicAuth = httpReq.GetBasicAuthUserAndPassword();
if (basicAuth == null)
throw HttpError.Unauthorized("Invalid BasicAuth credentials");
var userName = basicAuth.Value.Key;
var password = basicAuth.Value.Value;
return Authenticate(authService, session, userName, password, request.Continue);
}
If you want to provide enhanced behavior, I would simply inherit this class check for the Authorization: BasicToken header, if it exists use that otherwise call the base.Authenticate(...) method to perform the initial validation.