I need to get HttpContext.Session in GrantResourceOwnerCredentials method. However I get null when I try to access Httpcontext.Session.
Below is my code:
public void ConfigureAuth(IAppBuilder app)
{
PublicClientId = "self";
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(
PublicClientId,
DependencyResolver.Current.GetService<ApplicationUserManager>(),
HttpContext.Current),
//AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(3),
// In production mode set AllowInsecureHttp = false
AllowInsecureHttp = true,
RefreshTokenProvider = new RefreshTokenProvider(
DependencyResolver.Current.GetService<ApplicationDbContext>())
};
// Enable the application to use bearer tokens to authenticate users
app.UseOAuthBearerTokens(OAuthOptions);
}
I use SAML where I set my HttpContext session value, but I need to re-check that Session value again into my GrantResourceOwnerCredentials method, but however the session is always null in here.
...I need to re-check that Session value again [in] my GrantResourceOwnerCredentials method.
Checking the session value in the GrantResourceOwnerCredential method is not a good idea. The session is stored in the cookie that comes with the request. Since the request comes in through the token endpoint, the request is not protected against tampering with the cookie. That means that a malicious user could tamper with the cookie and change the session.
Related
I have implemented a custom AuthenticateAttribute, AuthUserSession and CredentialsAuthProvider. In the Execute method of my AuthenticateAttribute I do:
public override void Execute(IRequest request, IResponse response, object requestDto)
{
var session = request.GetSession() as IMyCustomAuthUserSession;
// Copy certain request headers into a dictionary on my session object
}
I need to store certain special headers that are sent to me for later use. This works correctly when authentication is not enabled. When authentication IS enabled and the user has to log in, the TryAuthenticate method of my CredentialsAuthProvider class fires:
public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
{
var session = authService.GetSession() as IMyCustomAuthUserSession;
}
The sessions in these to methods are not the same since the session I get in the TryAuthenticate method - which fires after the AuthenticateAttribute.Execute method - does not contain the headers I stored there.
The special headers are only sent in the very first call to the web server so I need to get them into the new session of the TryAuthenticate method.
How can I do this?
Passing Session data between logins is going to be difficult as Sessions are invalidated between Authentication attempts. You can choose to retain the same Session Cookies between logins by configuring the AuthFeature plugin with:
Plugins.Add(new AuthFeature(...) {
GenerateNewSessionCookiesOnAuthentication = false
});
Which will retain the same users ss-id/ss-pid cookies on Login.
Use SessionBag for persisting Data between Auth Sessinos
For persisting data outside of an Authenticated User Session you can use a SessionBag, e.g:
//Save
base.SessionBag["cart"] = new Cart { ... };
//Retrieve
var cart = base.SessionBag.Get<Cart>("cart");
Persisting User Data under a Custom Cookie
An alternative solution is to persist data under a Custom Cookie, that way it wont get invalidated by ServiceStack during Authentication.
Where you can register a Global Request Filter to ensure each client/browser has a custom Cookie Id, e.g:
GlobalRequestFilters.Add((req,res,dto) => {
var uniqueId = SessionExtensions.CreateRandomSessionId();
var httpRes = res as IHttpResponse;
httpRes.Cookies.AddPermanentCookie("my-id", uniqueId);
req.Items["my-id"] = uniqueId; //if also needed for this request
});
Then on subsequent requests you can persist data under your unique Cookie Id, e.g:
var uniqueId = req.GetSessionParam("my-id");
var cacheKey = $"urn:Cart:{uniqueId}";
var cache = req.GetCacheClient();
cache.Set(cacheKey, new Cart { ... });
Then later retrieve it with:
var uniqueId = req.GetSessionParam("my-id");
var cacheKey = $"urn:Cart:{uniqueId}";
var cache = req.GetCacheClient();
var cart cache.Get<Cart>(cacheKey);
I am using ServiceStack v4 with custom Authentication. This is setup and working correctly. I can call the /auth service and get a returned AuthorizationResponse with unique SessionId.
I also have swagger-ui plugin setup. Using it, I can authenticate via /auth and then call one of my other services which require authentication without issue.
Now, from a secondary MVC application using the c# JsonServiceClient I can again successfully make a call to /auth and then secured services using the same client object. However, if I dispose of that client (after saving the unique sessionId to a cookie), then later create a new client, and either add the sessionId as a Cookie or via headers using x-ss-pid as documented, calling a services returns 401. If I call a non-secure service, but then try to access the unique user session, it returns a new session.
If I look at the request headers in that service, the cookie or Header is clearly set with the sessionId. The sessionId also exists in the sessionCache. The problem seems to be that the code which tries to get the session from the request isn't finding it.
To be more specific, it appears that ServiceExtensions.GetSessionId is looking at the HostContext and not the calling Request. I'm not sure why. Perhaps I misunderstand something along the way here.
If I directly try and fetch my expected session with the following code it's found without issue.
var req = base.Request;
var sessionId = req.GetHeader("X-" + SessionFeature.PermanentSessionId);
var sessionKey = SessionFeature.GetSessionKey(sessionId);
var session = (sessionKey != null ? Cache.Get<IAuthSession>(sessionKey) : null)?? SessionFeature.CreateNewSession(req, sessionId);
So, am I missing something obvious here? Or maybe not so obvious in creating my secondary client?
Sample code of client calls
Here is my authorization code. It's contained in a Controller class. This is just the relevant parts.
using (var client = new JsonServiceClient(WebHelper.BuildApiUrl(Request)))
{
try
{
loginResult = client.Post(new Authenticate()
{
UserName = model.Email,
Password = model.Password,
RememberMe = model.RememberMe
});
Response.SetCookie(new HttpCookie(SessionFeature.PermanentSessionId, loginResult.SessionId));
return true;
}
}
Here is my secondary client setup and service call, contained in it's own controller class in another area of the MVC application
using (var client = new JsonServiceClient(WebHelper.BuildApiUrl(Request)))
{
var cCookie = HttpContext.Request.Cookies.Get(SessionFeature.PermanentSessionId);
if (cCookie != null)
{
client.Headers.Add("X-" + SessionFeature.PermanentSessionId, cCookie.Value);
client.Headers.Add("X-" + SessionFeature.SessionOptionsKey, "perm");
}
response = client.Get(new SubscriptionStatusRequest());
}
Additional Update
During the Authenticate process the following function is called from HttpRequestExtensions with the name = SessionFeature.PermanentSessionId
public static class HttpRequestExtensions
{
/// <summary>
/// Gets string value from Items[name] then Cookies[name] if exists.
/// Useful when *first* setting the users response cookie in the request filter.
/// To access the value for this initial request you need to set it in Items[].
/// </summary>
/// <returns>string value or null if it doesn't exist</returns>
public static string GetItemOrCookie(this IRequest httpReq, string name)
{
object value;
if (httpReq.Items.TryGetValue(name, out value)) return value.ToString();
Cookie cookie;
if (httpReq.Cookies.TryGetValue(name, out cookie)) return cookie.Value;
return null;
}
Now what occurs is the httpReq.Items contains a SessionFeature.PermanentSessionId value, but I have no clue why and where this gets set. I don't even understand at this point what the Items container is on the IRequest. The code thus never gets to the functionality to check my cookies or headers
The Session wiki describes the different cookies used by ServiceStack Session.
If the client wants to use a Permanent SessionId (i.e. ss-pid), it also needs to send a ss-opt=perm Cookie to indicate it wants to use the permanent Session. This Cookie is automatically set when authenticating with the RememberMe=true option during Authentication.
There was an issue in the Session RequestFilter that was used to ensure Session Id's were attached to the current request weren't using the public IRequest.GetPermanentSessionId() API's which also looks for SessionIds in the HTTP Headers. This has been resolved with this commit which now lets you make Session requests using HTTP Headers, e.g:
//First Authenticate to setup an Authenticated Session with the Server
var client = new JsonServiceClient(BaseUrl);
var authResponse = client.Send(new Authenticate
{
provider = CredentialsAuthProvider.Name,
UserName = "user",
Password = "p#55word",
RememberMe = true,
});
//Use new Client instance without Session Cookies populated
var clientWithHeaders = new JsonServiceClient(BaseUrl);
clientWithHeaders.Headers["X-ss-pid"] = authResponse.SessionId;
clientWithHeaders.Headers["X-ss-opt"] = "perm";
var response = clientWithHeaders.Send(new AuthOnly()); //success
This fix is available from v4.0.37+ that's now available on MyGet.
However, if I dispose of that client (after saving the unique sessionId to a cookie)
If the client is disposed where is the cookie you are saving the sessionId located? This answer might provide some additional information.
then later create a new client, and either add the sessionId as a Cookie or via headers using x-ss-pid as documented, calling a services returns 401
If you store/save a valid sessionId as a string you should be able to supply it within a CookieContainer of a new client (given the sessionId is still authenticated). I know you said you tried adding the sessionId as a Cookie but I don't a see sample within your question using the CookieContainer so it should look something like...
using (var client = new JsonServiceClient(WebHelper.BuildApiUrl(Request)))
{
var cCookieId = savedCookieId; //a string that I believe you saved from a successfully authenticated client that is now disposed
if (cCookieId != null)
{
var cookie = new Cookie(SessionFeature.PermanentSessionId, cCookieId);
//cookie.Domian = "somedomain.com" //you will probably need to supply this as well
client.CookieContainer.Add(cookie)
}
response = client.Get(new SubscriptionStatusRequest());
}
i have Asp.netMVC form authentication and another authentication which im handle the request, to handle my version i use this code to create session and
httpcontex:
HttpContext ctx = HttpContext.Current;
FormsAuthentication.SetAuthCookie(username, true);
ctx.Session["UserName"] = username;
var identity = new GenericIdentity(username);
IPrincipal principal = new GenericPrincipal(identity, new[] { "User" });
Thread.CurrentPrincipal = principal;
ctx.User = principal;
it works correct but after my view loaded completely, i send an Ajax Request and in the action which ajax calls HttpContext.Request.IsAuthenticated is false how can i make the httpcontext valid for all requests?
I think that you need set an user in httpcontex
System.Web.HttpContext.Current.User = user;
For me it was because of my browser settings code.
I changed my setting in the browser and this fixed your problem.
I am trying to understand the Asp.net Web Api Individual Accounts authentication and authorization. I have see several tutorials on the web including this one. In short, when a user agent provides username and password the API issues a token that the client will use in subsequent calls to the API for to identify itself. The user agent receives the token by making a request, typically to: http://example.com/Token. The path appears to be set in the Startup class like so:
TokenEndpointPath = new PathString("/Token")
My problem is, I can't find any controller methods that match that path. How does this work?
When you create a new Project with Individual Authentication in ASP.NET, the solution is created with an OAuth Provider to handle Authentication Request.
If you look at you solution, you should see a Providers Folder with a class ApplicationOAuthProvider.
This class implement all the logic for authenticate your members in you website.
The configuration is set at Startup to allow you to customize the url endpoint through the OAuthOption.
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(PublicClientId),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
AllowInsecureHttp = true
};
The TokenEndPoint Path properties defined the url which will fired the GrantResourceOwnerCredentials method of the GrandResourceOwnerCredentials.
If you use fiddler to authenticate and use this kind of body
grant_type=password&username=testUserName&password=TestPassword
you should pass in the following method :
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
ApplicationUser user = await userManager.FindAsync(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(userManager,
OAuthDefaults.AuthenticationType);
ClaimsIdentity cookiesIdentity = await user.GenerateUserIdentityAsync(userManager,
CookieAuthenticationDefaults.AuthenticationType);
AuthenticationProperties properties = CreateProperties(user.UserName);
AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(cookiesIdentity);
}
where context.UserName and context.Password are set with the data used in the request.
After the identity is confirmed (here using Entity Framework and a couple userName, Password in a database), a Bearer token is sent to the caller.
This Bearer token could then be used to be authenticated for the other calls.
Regards.
I have a Web API based application currently set up using the amazing Thinktecture IdentityModel 4.5.
It is set up for claims-based authentication, accepting a Basic auth credential sent in on the Authorization header. The javascript client saves the returned session token and uses this for subsequent requests by including it in the Authorization header preceded by Session as the scheme.
The javascript client also saves the token to a cookie, for retrieval if the window is closed and reopened quickly, or when new windows are opened to prevent the user having to re-authenticate. The cookie is named sessionToken and it's value is the actual token.
It all works wonderfully well.
The problem is I have a link on the app page that links to a direct address (/api/controller/id/pdfdocument) and opens it in a new window (target: _blank). Therefore there is no way to include the Authorization header in this request. However, the cookie is transferred over correctly as the session is still active.
I have tried to add a mapping to the AuthenticationConfig.Mappings collection to add support for collecting the token from the cookie, however I just can't get the configuration right to get this working, and havn't been able to find any other resources online. I'm assuming there's something very simple that needs to get fixed.
My code:
private static AuthenticationConfiguration CreateAuthenticationConfiguration()
{
var sessionTokenConfiguration = new SessionTokenConfiguration();
sessionTokenConfiguration.EndpointAddress = "/Authenticate";
sessionTokenConfiguration.DefaultTokenLifetime = new TimeSpan(1, 0, 0);
var authenticationConfig = new AuthenticationConfiguration
{
ClaimsAuthenticationManager = _authenticationManager,
RequireSsl = false,
EnableSessionToken = true,
SessionToken = sessionTokenConfiguration,
SendWwwAuthenticateResponseHeaders = false
};
var securityTokenHandler = new Thinktecture.IdentityModel.Tokens.Http.BasicAuthenticationWithRoleSecurityTokenHandler(_userService.ValidateUser, _userService.GetRolesForUser);
securityTokenHandler.RetainPassword = false;
var realm = "localhost";
var authorizationMapping = new AuthenticationOptionMapping
{
Options = AuthenticationOptions.ForAuthorizationHeader(scheme: "Basic"),
TokenHandler = new System.IdentityModel.Tokens.SecurityTokenHandlerCollection { securityTokenHandler },
Scheme = AuthenticationScheme.SchemeAndRealm("Basic", realm)
};
authenticationConfig.AddMapping(authorizationMapping);
var cookieMapping = new AuthenticationOptionMapping
{
Options = AuthenticationOptions.ForCookie("sessionToken"),
TokenHandler = new System.IdentityModel.Tokens.SecurityTokenHandlerCollection { securityTokenHandler },
Scheme = AuthenticationScheme.SchemeOnly(scheme: "Session")
};
authenticationConfig.AddMapping(cookieMapping);
//authenticationConfig.AddBasicAuthentication(_userService.ValidateUser, _userService.GetRolesForUser);
return authenticationConfig;
}
This configuration is then applied like so:
HttpConfiguration config;
var authenticationConfig = CreateAuthenticationConfiguration();
config.MessageHandlers.Add(new AuthenticationHandler(authenticationConfig));
And this is what the cookie looks like in the request header:
Cookie: sessionToken=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjEzNzM2NDA5NjgsImlzcyI6InNlc3Npb24gaXNzdWVyIiwiYXVkIjoiaHR0cDovL3Nlc3Npb24udHQvIiwiaHR0cDovL3NjaGVtYXMueG1sc29hcC5vcmcvd3MvMjAwNS8wNS9pZGVudGl0eS9jbGFpbXMvbmFtZSI6ImEiLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL2F1dGhlbnRpY2F0aW9ubWV0aG9kIjoiaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93cy8yMDA4LzA2L2lkZW50aXR5L2F1dGhlbnRpY2F0aW9ubWV0aG9kL3Bhc3N3b3JkIiwiaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93cy8yMDA4LzA2L2lkZW50aXR5L2NsYWltcy9hdXRoZW50aWNhdGlvbmluc3RhbnQiOiIyMDEzLTA3LTEyVDEzOjU2OjA4LjA5N1oiLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL3JvbGUiOiJBZG1pbmlzdHJhdG9yIiwiSWQiOiIyIn0.UlPeD9HzduQfwHE7NuXi9eMVo40hypi_LBK-f76VYFI; username=a
Any help most appreciated!
So after waiting a few minutes and receiving no replies and desperately needing this functionality I dived into the Thinktecture IdentityModel 4.5 source code to see what was going on and it seems this feature is not actually supported. Not only is it not supported but from the looks of it cookie mapping is not actually implemented.
I forked the repository and made a few small changes to allow for this feature:
https://github.com/ibraheemhlaiyil/Thinktecture.IdentityModel.45
and sent Dominick Baier of Thinktecture this in a pull request:
https://github.com/thinktecture/Thinktecture.IdentityModel.45/pull/95
Cookie usage has it's disadvantages, and it seems Thinktecture are trying to stay away from them as far as possible, however I could not come up with a different solution to my problem - a javascript client web applications that needs to open a new window/tab and maintain the authenticated session in the new window/tab.
If you want to use this feature, you simply set the new CookieName property on the SessionTokenConfiguration object. IdentityModel uses the HeaderName property to determine which header to look up for authentication data. In the same way, if the CookieName property is set this determines which cookie name is looked up for authentication data if no authentication data was found on the header.
In the example below, authentication data is looked for on the cookie named sessionToken if no authentication data is found on the Authorization header.
private static AuthenticationConfiguration CreateAuthenticationConfiguration()
{
var authenticationConfig = new AuthenticationConfiguration
{
ClaimsAuthenticationManager = _authenticationManager,
RequireSsl = false,
SendWwwAuthenticateResponseHeaders = false,
EnableSessionToken = true,
SessionToken = new SessionTokenConfiguration
{
EndpointAddress = "/Authenticate",
DefaultTokenLifetime = new TimeSpan(1, 0, 0),
HeaderName = "Authorization",
CookieName = "sessionToken",
SigningKey = CryptoRandom.CreateRandomKey(32)
}
};
authenticationConfig.AddBasicAuthentication(_userService.ValidateUser, _userService.GetRolesForUser);
return authenticationConfig;
}
As before, this configuration is applied like so during your application start up:
HttpConfiguration config;
var authenticationConfig = CreateAuthenticationConfiguration();
config.MessageHandlers.Add(new AuthenticationHandler(authenticationConfig));
The cookie authentication data has the exact same form as the data sent in the Authorization header, so if sent, the cookie should look like:
Cookie: sessionToken=Session eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjEzNzM2NDA5NjgsImlzcyI6InNlc3Npb24gaXNzdWVyIiwiYXVkIjoiaHR0cDovL3Nlc3Npb24udHQvIiwiaHR0cDovL3NjaGVtYXMueG1sc29hcC5vcmcvd3MvMjAwNS8wNS9pZGVudGl0eS9jbGFpbXMvbmFtZSI6ImEiLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL2F1dGhlbnRpY2F0aW9ubWV0aG9kIjoiaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93cy8yMDA4LzA2L2lkZW50aXR5L2F1dGhlbnRpY2F0aW9ubWV0aG9kL3Bhc3N3b3JkIiwiaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93cy8yMDA4LzA2L2lkZW50aXR5L2NsYWltcy9hdXRoZW50aWNhdGlvbmluc3RhbnQiOiIyMDEzLTA3LTEyVDEzOjU2OjA4LjA5N1oiLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL3JvbGUiOiJBZG1pbmlzdHJhdG9yIiwiSWQiOiIyIn0.UlPeD9HzduQfwHE7NuXi9eMVo40hypi_LBK-f76VYFI
Hope someone finds this of some use!