Accessing Session in HttpRequest Pipelines - sitecore

I'm trying to access a query string parameter and save it to a Session variable. Since the solution I'm working on has several base layouts, the simplest approach would be to add this to a pipeline handler. However, my code is failing because args.Context.Session is null:
public class SaveQueryStringToSession : HttpRequestProcessor
{
public override void Process(HttpRequestArgs args)
{
Assert.ArgumentNotNull((object)args, "args");
string queryString = WebUtil.GetQueryString("parm1");
if (queryString.Length <= 0)
return;
args.Context.Session["parm1"] = queryString;
}
}
This occurs when this method is inserted into either the HttpRequestBegin or HttpRequestEnd pipeline. Curious to know why, and if there is a standard workaround or pattern to use here. (Yes, I will add a null check. No need to point that out.)
I'm running Sitecore Sitecore.NET 6.4.1 (rev. 110720) on IIS 7.5 (Integrated .Net 2.0)
Possibly relevant links:
What is the first global.asax event that Session is accessible assuming the context handler is IRequiresSessionState or IReadOnlySessionState?
http://intothecore.cassidy.dk/2009/02/session-state-and-integrated-pipeline.html

The HttpRequestBegin pipeline is wired up to the HttpApplication.BeginRequest event, and this event is fired before the HttpSession object has been instantiated. Using the HttpRequestEnd pipeline does not work because the HttpSession object has already been disposed by the time the HttpApplication.EndRequest event is fired.
The session becomes available after the PostAcquireRequestState event is fired. To intercept this, create a class that implements IHttpModule, and add it to the <httpModules> element in Web.config. The HttpModule code will need to check if the request requires session state, as attempting to read the session for a static resource request will throw an exception.
Here is HttpModule code that accesses the Session and QueryString:
public class MyHttpModule :IHttpModule
{
public void Init(HttpApplication context)
{
context.PostAcquireRequestState += RequestHandler;
}
public void Dispose()
{
//
}
public void RequestHandler(object sender, EventArgs e)
{
var app = (HttpApplication) sender;
if (app.Context.Handler is IRequiresSessionState)
{
var session = app.Session;
var queryString = app.Request.QueryString["test"];
session["test"] = queryString;
}
}
}
It is worth noting that Sitecore's HttpRequestBegin and HttpRequestEnd pipelines are wired to ASP.NET via an HttpModule:
<add type="Sitecore.Nexus.Web.HttpModule,Sitecore.Nexus"
name="SitecoreHttpModule" />
Thanks to #ddysart for pointing me in the right direction, and to this answer for the correct event to listen for.

Actually instead of httpRequestBegin or HttpRequestEnd you can use httpRequestProcessed during which sitecore process the HttpRequest so you can access the Session.
You will be able to use the same code you have provided earlier.
public class SaveQueryStringToSession : HttpRequestProcessor
{
public override void Process(HttpRequestArgs args)
{
Assert.ArgumentNotNull((object)args, "args");
string queryString = WebUtil.GetQueryString("parm1");
if (queryString.Length <= 0)
return;
args.Context.Session["parm1"] = queryString;
}
}

Related

aws lambda function using serverless template of asp.net core

I don't have enough knowledge of aws but my company asked me to do a job which I guess is what AWS Lambda does perfectly. The requirement is I have to create a service that has an endpoint that needs to be called twice a day. The approach I followed is I created a serverless web API through visual studio and created API gateway endpoint for each endpoint. Then added a trigger through cloud watch events to run it twice a day but whenever the function is triggered I get this error.
Object reference not set to an instance of an object.: NullReferenceException
at Amazon.Lambda.AspNetCoreServer.APIGatewayProxyFunction.MarshallRequest(InvokeFeatures features, APIGatewayProxyRequest apiGatewayRequest, ILambdaContext lambdaContext)
at Amazon.Lambda.AspNetCoreServer.AbstractAspNetCoreFunction`2.FunctionHandlerAsync(TREQUEST request, ILambdaContext lambdaContext)
at lambda_method(Closure , Stream , Stream , LambdaContextInternal )
I have the same issue and could fix it recently.
If you use Lambda with ASP.NET Core, you should have LambdaEntryPoint class to handle all the requests.
Try to override MarshallRequest method in this class, add logging and see what you have in apiGatewayRequest parameter. The code can look something like this:
protected override void MarshallRequest(InvokeFeatures features, APIGatewayProxyRequest apiGatewayRequest, ILambdaContext lambdaContext)
{
LambdaLogger.Log($"Request path: {apiGatewayRequest.Path}");
LambdaLogger.Log($"Request path parameters: {apiGatewayRequest.PathParameters}");
LambdaLogger.Log($"Request body: {apiGatewayRequest.Body}");
LambdaLogger.Log($"Request request context: {apiGatewayRequest.RequestContext}");
base.MarshallRequest(features, apiGatewayRequest, lambdaContext);
}
In my case, all these values were nulls. The reason for it was in using Amazon EventBridge for keeping Lambda online to avoid a cold start. If you also use EventBridge, try to configure the request there properly. If not, you can try to update MarshalRequest the following way:
protected override void MarshallRequest(InvokeFeatures features, APIGatewayProxyRequest apiGatewayRequest, ILambdaContext lambdaContext)
{
if(apiGatewayRequest.RequestContext == null) //Or other property
{
return;
}
base.MarshallRequest(features, apiGatewayRequest, lambdaContext);
}
A few days ago I had the same problem. Grigory Zhadko’s answer helped me a lot by knowing which method I should overwrite. LambdaEntryPoint requires for any other process to instantiate an ApiGatewayProxiRequest object manually (for example, eventBridge). The configuration I implemented to fix the problem is as follows.
protected override void MarshallRequest(InvokeFeatures features, APIGatewayProxyRequest apiGatewayRequest, ILambdaContext lambdaContext)
{
var endpoint = "my/endpoint";
if (apiGatewayRequest != null && apiGatewayRequest?.RequestContext == null)
{
apiGatewayRequest.Path = $"/{endpoint}";
apiGatewayRequest.Resource = $"/{endpoint}";
apiGatewayRequest.HttpMethod = "ANY METHOD";
apiGatewayRequest.RequestContext = new APIGatewayProxyRequest.ProxyRequestContext
{
Path = $"/path/{endpoint}", // your path request
Identity = new APIGatewayProxyRequest.RequestIdentity
{
ClientCert = new APIGatewayProxyRequest.ProxyRequestClientCert
{
Validity = new APIGatewayProxyRequest.ClientCertValidity()
}
},
ResourcePath = $"/{basePath}{eventEntpoint}",
HttpMethod = "ANY METHOD",
Authorizer = new APIGatewayCustomAuthorizerContext()
};
}
base.MarshallRequest(features, apiGatewayRequest, lambdaContext);
}

Same-Site flag for session cookie in Spring Security

Is it possible to set Same-site Cookie flag in Spring Security?
And if not, is it on a roadmap to add support, please? There is already support in some browsers (i.e. Chrome).
New Tomcat version support SameSite cookies via TomcatContextCustomizer. So you should only customize tomcat CookieProcessor, e.g. for Spring Boot:
#Configuration
public class MvcConfiguration implements WebMvcConfigurer {
#Bean
public TomcatContextCustomizer sameSiteCookiesConfig() {
return context -> {
final Rfc6265CookieProcessor cookieProcessor = new Rfc6265CookieProcessor();
cookieProcessor.setSameSiteCookies(SameSiteCookies.NONE.getValue());
context.setCookieProcessor(cookieProcessor);
};
}
}
For SameSiteCookies.NONE be aware, that cookies are also Secure (SSL used), otherwise they couldn't be applied.
By default since Chrome 80 cookies considered as SameSite=Lax!
See SameSite Cookie in Spring Boot and SameSite cookie recipes.
For nginx proxy it could be solved easily in nginx config:
if ($scheme = http) {
return 301 https://$http_host$request_uri;
}
proxy_cookie_path / "/; secure; SameSite=None";
UPDATE from #madbreaks:
proxy_cookie_flags iso proxy_cookie_path
proxy_cookie_flags ~ secure samesite=none;
Instead of a Filter, In your Authentication Success Handler, you can mention in this way.
#Override
public void onAuthenticationSuccess(
HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException {
response.setStatus(HttpServletResponse.SC_OK);
clearAuthenticationAttributes(request);
addSameSiteCookieAttribute(response);
handle(request, response);
}
private void addSameSiteCookieAttribute(HttpServletResponse response) {
Collection<String> headers = response.getHeaders(HttpHeaders.SET_COOKIE);
boolean firstHeader = true;
// there can be multiple Set-Cookie attributes
for (String header : headers) {
if (firstHeader) {
response.setHeader(HttpHeaders.SET_COOKIE,
String.format("%s; %s", header, "SameSite=Strict"));
firstHeader = false;
continue;
}
response.addHeader(HttpHeaders.SET_COOKIE,
String.format("%s; %s", header, "SameSite=Strict"));
}
}
It was mentioned in one of the answers. Couldn't find the link after I've implemented it.
All possible solutions here failed for me. Every time I tried a filter or interceptor, the Set-Cookie header had not yet been added. The only way I was able to make this work was by adding Spring Session and adding this bean into one of my #Configuration files:
#Bean
public CookieSerializer cookieSerializer() {
DefaultCookieSerializer serializer = new DefaultCookieSerializer();
serializer.setSameSite("none");
return serializer;
}
Anyway hope this helps someone else in my same situation.
You can always set cookie values by yourself in the Java world if you can get an instance of the HttpServletResponse.
Then you can do:
response.setHeader("Set-Cookie", "key=value; HttpOnly; SameSite=strict")
In spring-security you can easily do this with a filter, here is an example:
public class CustomFilter extends GenericFilterBean {
#Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletResponse resp = (HttpServletResponse) response;
resp.setHeader("Set-Cookie", "locale=de; HttpOnly; SameSite=strict");
chain.doFilter(request, response);
}
}
Add this filter to your SecurityConfig like this:
http.addFilterAfter(new CustomFilter(), BasicAuthenticationFilter.class)
Or via XML:
<http>
<custom-filter after="BASIC_AUTH_FILTER" ref="myFilter" />
</http>
<beans:bean id="myFilter" class="org.bla.CustomFilter"/>
It isn't possible. There is support for this feature in Spring Session: https://spring.io/blog/2018/10/31/spring-session-bean-ga-released
I came up with a solution similar to Ron's one. But there is one important thing to note:
Cookies for cross-site usage must specify SameSite=None; Secure
to enable inclusion in third party context.
So I've included Secure attribute in header. Also, you don't have to override all three methods when you don't use them. It is only required when you are implementing HandlerInterceptor.
import org.apache.commons.lang.StringUtils;
public class CookiesInterceptor extends HandlerInterceptorAdapter {
final String sameSiteAttribute = "; SameSite=None";
final String secureAttribute = "; Secure";
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response,
Object handler, ModelAndView modelAndView) throws Exception {
addEtagHeader(request, response);
Collection<String> setCookieHeaders = response.getHeaders(HttpHeaders.SET_COOKIE);
if (setCookieHeaders == null || setCookieHeaders.isEmpty())
return;
setCookieHeaders
.stream()
.filter(StringUtils::isNotBlank)
.map(header -> {
if (header.toLowerCase().contains("samesite")) {
return header;
} else {
return header.concat(sameSiteAttribute);
}
})
.map(header -> {
if (header.toLowerCase().contains("secure")) {
return header;
} else {
return header.concat(secureAttribute);
}
})
.forEach(finalHeader -> response.setHeader(HttpHeaders.SET_COOKIE, finalHeader));
}
}
I used xml in my project so I had to add this to my configuration file:
<mvc:interceptors>
<bean class="com.zoetis.widgetserver.mvc.CookiesInterceptor"/>
</mvc:interceptors>
Using the interceptor in SpringBoot.
I'm looking for a resolution for adding SameSite as you, and I only want to add the attribute to the existing "Set-Cookie" instead of creating a new "Set-Cookie".
I have tried several ways to meet this requirement, including:
adding a custom filter as #unwichtich said,
and more I overrode basicAuthenticationFilter. It does add the SameSite attribute. While the timing when Spring will add the "Set-Cookie" is hard to catch. I thought in onAuthenticationSuccess() method, the response must have this header, but it doesn't. I'm not sure whether it's the fault of my custom basicAuthenticationFilter's order.
using cookieSerializer, but the spring-session version comes up to a problem. Seems only the latest version support it, but I still can't figure out the version number should be added into the dependency list.
Unfortunately, none of them above can add the samesite well as expected.
Finally, I found the interceptor in spring can help me to make it.
It took me a week to get it. Hope this can help you if anyone has the same problem.
#Component
public class CookieServiceInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(
HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return true;
}
#Override
public void postHandle(
HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
//check whether it has "set-cookie" in the response, if it has, then add "SameSite" attribute
//it should be found in the response of the first successful login
Collection<String> headers = response.getHeaders(HttpHeaders.SET_COOKIE);
boolean firstHeader = true;
for (String header : headers) { // there can be multiple Set-Cookie attributes
if (firstHeader) {
response.setHeader(HttpHeaders.SET_COOKIE, String.format("%s; %s", header, "SameSite=strict"));
firstHeader = false;
continue;
}
response.addHeader(HttpHeaders.SET_COOKIE, String.format("%s; %s", header, "SameSite=strict"));
}
}
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception exception) throws Exception {
}
}
and you also need to make this interceptor work in your application, which means you should add a bean as below:
#Autowired
CookieServiceInterceptor cookieServiceInterceptor;
#Bean
public MappedInterceptor myInterceptor() {
return new MappedInterceptor(null, cookieServiceInterceptor);
}
This interceptor has a flaw, it can't add samesite when the request is redirected(ex.return 302) or failed(ex. return 401), while it makes my app fail when SSO. Eventually, I have to use the Tomcat cookie, because I don't embed tomcat in my springboot app. I add
<Context>
<CookieProcessor sameSiteCookies="none" />
</Context>
in a context.xml under /META-INF of my app. It will add SameSite attribute in set-cookie header for each response. Note that this behavior is possible since Tomcat 9.0.21 and 8.5.42. according to https://stackoverflow.com/a/57622508/4033979
For Spring Webflux (reactive environment) this worked for me:
#Configuration
#EnableSpringWebSession
public class SessionModule {
#Bean
public ReactiveSessionRepository<MapSession> reactiveSessionRepository() {
return new ReactiveMapSessionRepository(new ConcurrentHashMap<>());
}
#Bean
public WebSessionIdResolver webSessionIdResolver() {
CookieWebSessionIdResolver resolver = new CookieWebSessionIdResolver();
resolver.setCookieName("SESSION");
resolver.addCookieInitializer((builder) -> {
builder.path("/")
.httpOnly(true)
.secure(true)
.sameSite("None; Secure");
});
return resolver;
}
}
You can add cookie by yourself by using ResponseCookie and adding it to your HttpServletResponse.
ResponseCookie cookie = ResponseCookie.from("cookiename", "cookieValue")
.maxAge(3600) // one hour
.domain("test.com")
.sameSite("None")
.secure(true)
.path("/")
.build();
response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString());
I have tested this solution for spring-webmvc without spring-security, but I think it should also work for spring-boot.
Using the SessionRepositoryFilter bean from spring-session-core
You can extend default java HttpSession with a spring Session and replace JSESSIONID cookie with a custom one, like this:
Set-Cookie: JSESSIONID=NWU4NzY4NWUtMDY3MC00Y2M1LTg1YmMtNmE1ZWJmODcxNzRj; Path=/; Secure; HttpOnly; SameSite=None
Additional spring Session cookie flags can be set using DefaultCookieSerializer:
#Configuration
#EnableSpringHttpSession
public class WebAppConfig implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext) {
servletContext
.addFilter("sessionRepositoryFilter", DelegatingFilterProxy.class)
.addMappingForUrlPatterns(null, false, "/*");
}
#Bean
public MapSessionRepository sessionRepository() {
final Map<String, Session> sessions = new ConcurrentHashMap<>();
MapSessionRepository sessionRepository =
new MapSessionRepository(sessions) {
#Override
public void save(MapSession session) {
sessions.entrySet().stream()
.filter(entry -> entry.getValue().isExpired())
.forEach(entry -> sessions.remove(entry.getKey()));
super.save(session);
}
};
sessionRepository.setDefaultMaxInactiveInterval(60*5);
return sessionRepository;
}
#Bean
public SessionRepositoryFilter<?> sessionRepositoryFilter(MapSessionRepository sessionRepository) {
SessionRepositoryFilter<?> sessionRepositoryFilter =
new SessionRepositoryFilter<>(sessionRepository);
DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
cookieSerializer.setCookieName("JSESSIONID");
cookieSerializer.setSameSite("None");
cookieSerializer.setUseSecureCookie(true);
CookieHttpSessionIdResolver cookieHttpSessionIdResolver =
new CookieHttpSessionIdResolver();
cookieHttpSessionIdResolver.setCookieSerializer(cookieSerializer);
sessionRepositoryFilter.setHttpSessionIdResolver(cookieHttpSessionIdResolver);
return sessionRepositoryFilter;
}
}
I have extended a bit MapSessionRepository implementation, since it does NOT support firing SessionDeletedEvent or SessionExpiredEvent - I have added clearing of expired sessions before adding new ones. I think this might be enough for a small application.
Apparently, with spring boot you can write this and it gets picked up.
#Configuration
public static class WebConfig implements WebMvcConfigurer {
#Bean
public CookieSameSiteSupplier cookieSameSiteSupplier(){
return CookieSameSiteSupplier.ofNone();
}
}
Or ... even simpler, spring boot since 2.6.0 supports setting it in application.properties.
Spring documentation about SameSite Cookies
server.servlet.session.cookie.same-site = none

Camel exchange expired via jetty continuation

Is there a possibility in Apache Camel to register a handler for managing exchanges that cannot be written to jetty endpoint http response because continuation timeout has been reached?
I'll just add my notes on that because I made it available in my project by modifying CamelContinuationServlet in the if (continuation.isExpired()) block like this
if (continuation.isExpired()) {
String id = (String) continuation.getAttribute(EXCHANGE_ATTRIBUTE_ID);
// remember this id as expired
expiredExchanges.put(id, id);
log.warn("Continuation expired of exchangeId: {}", id);
consumer.getBinding().doWriteExceptionResponse(new TimeoutException(), response);
return;
}
in combination with a custom HttpBinding called ErrorHandlingHttpBinding in my code like this
public class ErrorHandlingHttpBinding extends DefaultHttpBinding {
#Override
public void doWriteExceptionResponse(Throwable exception, HttpServletResponse response) throws IOException {
if (exception instanceof TimeoutException) {
response.setStatus(HttpServletResponse.SC_GATEWAY_TIMEOUT);
response.getWriter().write("Continuation timed out...");
} else {
super.doWriteExceptionResponse(exception, response);
}
}
}
registered as spring bean with id="errorHandlingHttpBinding" and referred in the component string as jetty:http://localhost:21010/?useContinuation=true&continuationTimeout=1&httpBindingRef=errorHandlingHttpBinding.
No this is not possible. Maybe you need to set a higher timeout if you have some slow processing exchanges.
You are welcome to dive in the Jetty APIs to see if you can find a hook for such a onTimeout event and see what it takes to support that in camel-jetty.

Variable cookie path with ASP.NET Identity

We migrated a multitenant MVC application from ASP.NET Membership Provider to ASP.NET Identity.
This is my Startup.Auth.cs (simplified):
public partial class Startup
{
public void ConfigureAuth(IAppBuilder app)
{
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
Provider = new CookieAuthenticationProvider
{
OnValidateIdentity =
SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, Identity, int>(
TimeSpan.FromMinutes(30),
(manager, user) =>
manager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie),
clIdentity => clIdentity.GetUserId<int>())
}
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
}
In our multitenant application, each tenant has its own 'slug' (e.g. http://example.com/tenant1/ and http://example.com/tenant2/)
However, currently, the cookies are stored in the root. This causes security issues as users from tenant1 are automatically logged in on the website from tenant2.
How can we make the CookiePath (in CookieAuthenticationOptions) variable so that it changes depending on the tenant?
I fixed this issue with a lot of help from dampee.
The CookiePath in the CookieAuthenticationOptions object is evaluated only once: at application startup.
The easiest solution (workaround) was to create a derived CookieAuthenticationProvider that overrides ResponseSignIn and ResponseSignOut.
They both have an argument called context which has a property called CookiePath. Modify this property in both of these methods to change the CookiePath.
You can also use the class I created.
Then all you have to do is replace the CookieAuthenticationProvider in the CookieAuthenticationOptions with the one you just created.
This works for the ApplicationCookie. The ExternalSignInCookie doesn't matter that much since it is used only temporarily while signing in with an external login.
Improving on SamuelDebruyn's own solution, I found you can pass the path from the SignIn call to the provider using an AuthenticationProperties object. This way, instead of extracting the path from the request context as his gist shows, you can pass it explicitly from the source:
// method inside web api controller
private void SignIn(string name, string cookiePath)
{
var claims = new[] { new Claim(ClaimTypes.Name, name) };
var identity = new ClaimsIdentity(claims, "ApplicationCookie");
var options = new AuthenticationProperties();
options.Dictionary["CustomCookiePath"] = cookiePath;
var authManager = Request.GetOwinContext().Authentication;
authManager.SignIn(options, identity);
}
// Startup.cs
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
Provider = new CustomCookieProvider()
});
// custom provider
public class CustomCookieProvider : CookieAuthenticationProvider
{
public override void ResponseSignIn(CookieResponseSignInContext context)
{
context.CookieOptions.Path = context.Properties.Dictionary["CustomCookiePath"];
base.ResponseSignIn(context);
}
}
You can use a custom ICookieManager to dynamically return the cookie value to the CookieAuthenticationProvider based on whatever is in the request, to do this you would still maintain the CookiePath as "/" and then leave it up to the ICookieManager to return (or write) the cookie however you want. The CookieManager is an option on the CookieAuthenticationOptions. I blogged about this here: http://shazwazza.com/post/owin-cookie-authentication-with-variable-cookie-paths/

ServiceStack web services security

Hi I am new to working with Servicestack and have downloaded their very comprehensive bootstrapapi example and am working with it, but am still having some issues. The issue is with security, what is happening is I am getting 405 errors when trying to access the protected services. Using the authenticate service it appears that I am authenticating correctly. Please help and explain. Here is the code:
public class Hello
{
public string Name { get; set; }
}
public class AuthHello
{
public string Name { get; set; }
}
public class RoleHello
{
public string Name { get; set; }
}
public class HelloResponse
{
public string Result { get; set; }
}
The Services:
public class HelloService : ServiceBase<Hello>
{
//Get's called by all HTTP Verbs (GET,POST,PUT,DELETE,etc) and endpoints JSON,XMl,JSV,etc
protected override object Run(Hello request)
{
return new HelloResponse { Result = "Hello, Olle är en ÖL ål " + request.Name };
}
}
[Authenticate()]
public class AuthHelloService : RestServiceBase<AuthHello>
{
public object Execute(Hello request)
{
return new HelloResponse { Result = "Hello, " + request.Name };
}
}
[RequiredRole("Test")]
public class RoleHelloService : RestServiceBase<RoleHello>
{
public object Execute(Hello request)
{
return new HelloResponse { Result = "Hello, " + request.Name };
}
}
Here is the AppHost:
public class HelloAppHost : AppHostBase
{
//Tell Service Stack the name of your application and where to find your web services
public HelloAppHost() : base("Hello Web Services", typeof(HelloService).Assembly) { }
public override void Configure(Container container)
{
//Register all Authentication methods you want to enable for this web app.
Plugins.Add(new AuthFeature(() => new AuthUserSession(), new IAuthProvider[] {new CustomCredentialsAuthProvider(), //HTML Form post of UserName/Password credentials
}));
container.Register<ICacheClient>(new MemoryCacheClient() { FlushOnDispose = false });
//register user-defined REST-ful urls
Routes
.Add<Hello>("/hello")
.Add<Hello>("/hello/{Name}")
.Add<AuthHello>("/AuthHello")
.Add<RoleHello>("/RoleHello");
}
}
UPDATE
Everything works as expect if you replace : RestServiceBase with : ISevice so now the question is why.
Check the wiki documentation first
I would first go through the documentation in ServiceStack's Authentication Wiki to get a better idea about how ServiceStack's Authentication works. There's a lot of documentation in the wiki, so if you're unsure of something you should refer to that first. It's a community wiki so feel free to expand whats there if you think it can help others.
Refer to the implementation in the source code if behavior is not clear
If you're unsure of what something does you should refer to the RequiredRole source code as the master authority as how it works. RequiredRole is just a Request Filter Attribute which gets run before every service that has the attribute.
The RequiredRole attribute just calls your session.HasRole() method as seen here:
public bool HasAllRoles(IAuthSession session)
{
return this.RequiredRoles
.All(requiredRole => session != null
&& session.HasRole(requiredRole));
}
Because it just calls your session you can override the implementation of session.HasRole() if you have a custom session.
Registering and Implementing a CustomUserSession
The Social BootstrapApi project does implement its own CustomSession that it registers here but does not override the HasRole() implementation so it uses the built-in implementation in the base AuthUserSession.HasRole() which simply looks like the Roles collection to see if the user has the specified role in their Session POCO:
public virtual bool HasRole(string role)
{
return this.Roles != null && this.Roles.Contains(role);
}
Session properties populated by AuthUserRepository
The Roles property (as well as most other properties on a users Session) is populated by the AuthUserRepository that you have specified e.g. if you're using the OrmLiteAuthRepository like SocialBootstrapApi does here than the Roles attribute is persisted in the Roles column in the UserAuth RDBMS table. Depending on the AuthUserRepository you use the UserAuth / UserOAuthProvider POCOs get stored as RDBMS tables in OrmLite or as text blobs in Redis, etc.
Manage roles and permissions with AssignRoles / UnAssignRoles services
So for a user to have the required role (and authorization to pass), it should have this Role added to its UserAuth db row entry. ServiceStack's AuthFeature includes 2 services for managing users permissions and roles:
/assignroles
/unassignroles
How to initially give someone the Admin Role
These services does require a user with the Admin Role to be already authenticated.
You can do this by manually changing a specific users UserAuth.Role column to include the value "Admin". The Social Bootstrap API project instead does this by handling the OnAuthenticated() event on its CustomUserSession that simply checks to see if the authenticated username is declared in the Web.Config and if it is, calls the AssignRoles service giving that authenticated user the Admin Role:
if (AppHost.Config.AdminUserNames.Contains(session.UserAuthName)
&& !session.HasRole(RoleNames.Admin))
{
var assignRoles = authService.ResolveService<AssignRolesService>();
assignRoles.Execute(new AssignRoles {
UserName = session.UserAuthName,
Roles = { RoleNames.Admin }
});
}