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")]
?
Related
I'm using ASP.NET Identity and OpenIddict for a custom authorization server in MVC / Razor Pages. I'm supporting most OpenID flows with OpenIddict and user and admin areas with ASP.NET Identity.
The site is accessed directly from desktop browsers, mobile apps and other projects, especially the endpoints for starting a login and a authroization flow.
Everything is working as expected.
Now I would like to add partial multi-tenancy by only switching CSS based on the tenant. The rest of the app will remain untouched. I was going to start by modifying the _Styles.cshtml file by adding the following:
#using Microsoft.AspNetCore.Http
#inject IHttpContextAccessor accessor
#{
var ok = accessor.HttpContext.Request.Headers.TryGetValue("X-Tenant-Id", out var values);
var tenantId = values.FirstOrDefault();
}
Then depending on the tenant I will add the CSS file.
How, through the whole app, in views and in controller actions there redirects and links to other pages (eg. from login page to register page or forgot password). This is a problem because the custom header above is not persisted in further requests.
So I was thinking of added the header to the session state. Currently I'm not using sessions, but I would be doing something similar to what is described in this MS Article.
Any request that has the above header will update the session state. As this is an essential cookie but I still have to use cookie consent policy and data protection, is it possible to flag it as essential to avoid cookie consent?
Will the work or is there a better solution?
Is it possible to flag it as essential to avoid cookie consent
If you marked the session cookie as essential ,you would reveive a cookie name of .AspNetCore.Session
builder.Services.AddSession(options =>
{
options.Cookie.IsEssential = true;
})
and if you configured cookieConsent as below
builder.Services.Configure<CookiePolicyOptions>(op =>
{ op.CheckConsentNeeded = _ => true;
});
you won't receive the cookies which are not marked as essential
I tried as below:
public IActionResult Index()
{
HttpContext.Response.Cookies.Append("SomeKey", "SomeValue" ,new CookieOptions() { IsEssential=false});
HttpContext.Session.SetString("SomeKey", "SomeVal");
return View();
}
public IActionResult Privacy()
{
string? cookieval;
HttpContext.Request.Cookies.TryGetValue("SomeKey", out cookieval);
var sessionval=HttpContext.Session.GetString("SomeKey");
return View();
The Result:
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.
Exploring the ServiceStack authentication providers for the first time. Have gradually built up a test project by adding BasicAuthProvider and when that worked, added GithubAuthProvider. The last one added was JwtAuthProvider and that works as well. When I retested authentication with any of the previous authentication providers such as Github, for example, I find that the JwtAuthProvider lambda function for CreatePayloadFilter is still being invoked. This was not expected.
The AuthFeature plugin is as follows:
Plugins.Add(new AuthFeature(
() => new PartnerWebSession(),
new IAuthProvider[] {
new JwtAuthProvider(appSettings) { ... },
new BasicAuthProvider(), //Sign-in with HTTP Basic Auth
new GithubAuthProvider(appSettings)
}));
I have created BasicHello, GithubHello and JwtHello service models with different end points for use with each of the authentication providers with routes:
/basic/{Name}
/github/{Name}
/jwt/{Name}
The AuthenticateAttribute has been attached to the Service classes designed to work with these models and they each specify the Provider to use, e.g.:
[Authenticate(Provider = "basic")]
public class BasicAuthentication : Service
{
public object Any(BasicHello request)
{
return new BasicHelloResponse { Result = $"{nameof(BasicAuthentication)} says Hello, {request.Name}!" };
}
}
Testing using Visual Studio debug and Chrome browser. Have ensured that no previous cookies JWT are hanging around from previous tests by clearing cookies for all time. I enter the URL:
http://localhost:52070/api/github?name=stephen
This takes me to the ServiceStack rendered authentication screen with login dialogue and "Sign In with Github" button. Click the "Sign In with Github" button which takes me to the "Sign in to GitHub" page provided by GitHub. Successfully authenticate, and then code execution hits a breakpoint I've set in CreatePayloadFilter lambda of the JwtAuthProvider.
I did not expect this. Is this an error or am I doing something wrong?
If you have the JWT AuthProvider registered it populates the successful Auth Response with a populated JWT Token that encapsulates the partial authenticated session.
The stateless client JWT Token (i.e. instead of server session cookies) can then be used to make authenticated requests after a successful OAuth Sign In.
Hi am trying to use OAuth authentication FacebookAuthProvider provided by servicestack
var AppSettings = appHost.AppSettings;
appHost.Plugins.Add(new AuthFeature(() => new CustomUserSession(),
new IAuthProvider[] {
new CredentialsAuthProvider(AppSettings),
new FacebookAuthProvider(AppSettings),
new GoogleAuthProvider(AppSettings)
}));
I have implement custom AuthEvents and I have access to authorized user data
but after RedirectUrl happens in endpoint session is empty and IsAuthenticated property is false
In the same time from Controller I can see that created session successfully saved in Cache
This scenario occurred only if user doesn't login in facebook in the browser yet, but for user that already logged in facebook, authentication works fine
What am I doing wrong ?
Thanks in advance!
Updates:
Scenario for repsoducing:
ensure that you didn't loged in facebook in browser (open facebook.com and log out if need)
run web app (project which created with template mvcauth)
try to login via facebook
3.1. for first attempt you will be redirected to https://www.facebook.com/login.php? page for specify your facebook account credentials
after first attempt you will be returned to web app page /Account/Login#s=1 but User.Identity.IsAuthenticated = false in AccountController.Login and you still unauthorized.
after second attempt you will finally successfully logged in
At the same time after first attempt Session with user data will be created and saved in Cache, it looks like after redirecting from page 'https://www.facebook.com/login.php' а new session being created without auth data, but for second attempt we successfully login, maybe the core of issue is redirection from 'https://www.facebook.com/login.php'
In order to be able to use ServiceStack.Auth in MVC and vice-versa you'll need to register the NetCoreIdentityAuthProvider which provides integration between ServiceStack Authenticated Sessions and ASP.NET Core's Claims Principal, e.g:
public void Configure(IAppHost appHost)
{
var AppSettings = appHost.AppSettings;
appHost.Plugins.Add(new AuthFeature(() => new CustomUserSession(),
new IAuthProvider[] {
new NetCoreIdentityAuthProvider(AppSettings), /* Use ServiceStack Auth in MVC */
new CredentialsAuthProvider(AppSettings), /* Sign In with Username / Password */
new FacebookAuthProvider(AppSettings),
new GoogleAuthProvider(AppSettings),
new MicrosoftGraphAuthProvider(AppSettings),
}));
appHost.Plugins.Add(new RegistrationFeature()); //Enable /register Service
//override the default registration validation with your own custom implementation
appHost.RegisterAs<CustomRegistrationValidator, IValidator<Register>>();
}
I creating a website which has register link multiple auth providers and custom token as well. I also using AngularFire2 to communicate between Angular2 and Firebase but seem it doesn't have method similar with Firebase, e.g:
var ref = new Firebase("https://<YOUR-FIREBASE-APP>.firebaseio.com/");
ref.authWithCustomToken(AUTH_TOKEN, function(error, authData) {
Anyone can show up to me how can deal with issue?
To authenticate using a custom token, you can call AngularFire2's login method with the following configuration options:
angularFire.auth.login(AUTH_TOKEN, {
provider: AuthProviders.Custom,
method: AuthMethods.CustomToken
});
Internally, this will call Firebase's signInWithCustomToken method.