I am trying to send the Session Id of an ASP.NET Core 5 application using Razor Pages to my SignalR Core Hub. However, the Session Id is only added to the negotiate request, not to the actual WebSocket that is being opened:
How can I add it to the sessionsHub request as well, which is used by the OnConnected() method in the hub?
The Razor Page .cs:
public class IndexModel : PageModel
{
private readonly HttpContext _httpContext;
public string SessionId { get; set; }
public IndexModel(IHttpContextAccessor httpContextAccessor)
{
_httpContext = httpContextAccessor.HttpContext;
}
public async Task OnGet()
{
SessionId = _httpContext.Session.Id;
}
}
The .cshtml using a querystring, I've also tried adding a Session-Id as Header to the request, same result:
#page
#model IndexModel
<script type="text/javascript">
var sessionId = "#Model.SessionId";
class CustomHttpClient extends signalR.DefaultHttpClient {
send(request) {
var url = new URL(request.url);
if(!url.search){
url.href = url.href + '?sessionId="' + sessionId + '"';
}else{
url.href = url.href + '&sessionId="' + sessionId + '"';
}
request.url = url.href;
return super.send(request);
}
}
var connection = new signalR.HubConnectionBuilder().withUrl("/sessionsHub", { httpClient: new CustomHttpClient() }).build();
connection.start().then(() => {
}).catch(function (err) {
return console.error(err.toString());
});
</script>
The hub:
public class SessionHub : Hub{
public override async Task OnConnectedAsync()
{
await base.OnConnectedAsync();
string sessionId = GetSessionId();
}
private string GetSessionId()
{
HttpContext httpContext = Context.GetHttpContext();
List<StringValues> sessionIdQueryString = httpContext.Request.Query.Where(x => x.Key == "sessionId").Select(x => x.Value).ToList();
if (sessionIdQueryString.Count == 0)
{
throw new NullReferenceException();
}
string sessionId = sessionIdQueryString.First();
return sessionId;
}
}
You need to enter the sessionid value inside the url when creating the connetion.
so that it can be accessed in all hub methods The code will look like this :
var connection = new signalR.HubConnectionBuilder()
.withUrl("/sessionsHub/?sessionId=#Model.SessionId")
.build();
connection.start().then(function(){
//ToDo
}).catch(function (err) {
console.log(err);
});
Related
I'm making a shopping cart using ASP.NET Core Web API and react. When testing what was done in Swagger UI and Postman, everything works correctly, but when switching to react, problems begin. Each time when I add product in swagger/postman, all products are added with their ShoppingCartId that is related to the current session. But when I tested it with React, after each added product a new ShoppingCartId is created, which shouldn't happen. Below is the code and result:
Result in DB
Session Configuration:
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddScoped(sc => ShoppingCart.GetShoppingCart(sc));
services.AddDistributedMemoryCache();
services.AddSession(options => {
options.IdleTimeout = TimeSpan.FromMinutes(3600);
options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true;
});
ShoppingCart class:
public class ShoppingCart
{
public AppDbContext _context { get; set; }
public string ShoppingCartId { get; set; }
public List<ShoppingCartItem> ShoppingCartItem { get; set; }
public ShoppingCart(AppDbContext context)
{
_context = context;
}
public static ShoppingCart GetShoppingCart (IServiceProvider services)
{
ISession session = services.GetRequiredService<IHttpContextAccessor>()?.HttpContext.Session;
var context = services.GetService<AppDbContext>();
string cardId = session.GetString("CardId") ?? Guid.NewGuid().ToString();
session.SetString("CardId", cardId);
return new ShoppingCart(context) { ShoppingCartId = cardId };
}
public List<ShoppingCartItem> GetShoppingCartItems()
{
return ShoppingCartItem ?? (ShoppingCartItem = _context.ShoppingCartItem.Where(n => n.ShoppingCartId ==
ShoppingCartId).Include(n => n.Book).ToList());
}
public void AddItemToCart(Book book)
{
var shoppingCartItem = _context.ShoppingCartItem.FirstOrDefault(n => n.Book.Id == book.Id &&
n.ShoppingCartId == ShoppingCartId);
if(shoppingCartItem == null)
{
shoppingCartItem = new ShoppingCartItem()
{
ShoppingCartId = ShoppingCartId,
Book = book,
Amount = 1
};
_context.ShoppingCartItem.Add(shoppingCartItem);
}
else
{
shoppingCartItem.Amount++;
}
_context.SaveChanges();
}
}
Controller:
[Route("api/[controller]")]
[ApiController]
public class OrderController : ControllerBase
{
private readonly ShoppingCart _shoppingCart;
private readonly IOrderService _orderService;
public OrderController(ShoppingCart shoppingCart, IOrderService orderService)
{
_shoppingCart = shoppingCart;
_orderService = orderService;
}
[HttpGet("get-shopping-cart")]
public IActionResult GetShoppingCart()
{
var items = _shoppingCart.GetShoppingCartItems();
_shoppingCart.ShoppingCartItem = items;
var responce = new ShopCartVM()
{
ShoppingCartItem = items,
ShopCartTotal = _shoppingCart.GetShoppingCartTotal(),
TotalItems = _shoppingCart.GetShoppingCartItemsSummary()
};
return Ok(responce);
}
[HttpPost("add-item-to-cart")]
public async Task<IActionResult> AddItemToShoppingCart(int id)
{
var item = await _bookService.GetByIdShopAsync(id);
if (item != null)
{
_shoppingCart.AddItemToCart(item);
}
return RedirectToAction(nameof(GetShoppingCart));
}
}
React
export const addProduct = (productId: number) => async (dispatch: AppDispatch) => {
try {
const request = new XMLHttpRequest();
request.open("POST", `https://localhost:44307/api/Order/add-item-to-cart?id=${productId}`)
request.responseType = "text"
request.onload = function () {
const response = request.response;
console.log(response)
};
request.send();
} catch (e) {
console.log(e)
}
}
Update: I think the problem is that in React a new session is generated after each action, which does not happen in Swagger. What could be the reason for this and how to fix it?
Below is the screenshots with changes and static session:
.AspNetCore.Session React
.AspNetCore.Session React
.AspNetCore.Session Swagger
.AspNetCore.Session Swagger
You can remove app.UseCookiePolicy() . This entry will block cookies until you confirm that you agree to receive them. Also provide your cookie name options.Cookie.Name = "YourName". In options.Cookie.HttpOnly = true, you need to change the value to false, otherwise the browser will not be able to read the cookie. Also specify options.Cookie.SameSite = SameSiteMode.None to set the cookie to your react app.
I think that something among all this should help you, correct me if I have indicated something incorrectly.
I do believe that before you put an item to your cart you obtain a cart from the get-shopping-cart method. You can contain here Id of the shopping cart and set it somewhere on your frontend side, then you can pass productId and cartId to a add-item-to-cart endpoint and have your cartId always contained in the request.
Also I found this What is the best way to manage a user's session in React? question which may help you as well.
Btw. On the GetShoppingCart method you call it cardId instead of carrId> Don't you have a typo?
[authorise]
public string Get()
{
return "value1";
}
if I am not authorised it will return a status of 401 not authorised.
can it return a value such as json "{status:false,code:"401"}". ?
According to your description, I suggest you could try to use custommiddleware to achieve your requirement.
You could captured the 401 error in middleware and then rewrite the response body to {status:false,code:"401"}
More details, you could add below codes into Configure method above the app.UseAuthentication();:
app.Use(async (context, next) =>
{
await next();
if (context.Response.StatusCode == 401)
{
await context.Response.WriteAsync("{status:false,code:'401'}");
}
});
Result:
You can create a custom authorize attribute using IAsyncAuthorizationFilter.
public class CustomAuthorizeFilter : IAsyncAuthorizationFilter
{
public AuthorizationPolicy Policy { get; }
public CustomAuthorizeFilter(AuthorizationPolicy policy)
{
Policy = policy ?? throw new ArgumentNullException(nameof(policy));
}
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
// Allow Anonymous skips all authorization
if (context.Filters.Any(item => item is IAllowAnonymousFilter))
{
return;
}
var policyEvaluator = context.HttpContext.RequestServices.GetRequiredService<IPolicyEvaluator>();
var authenticateResult = await policyEvaluator.AuthenticateAsync(Policy, context.HttpContext);
var authorizeResult = await policyEvaluator.AuthorizeAsync(Policy, authenticateResult, context.HttpContext, context);
if (authorizeResult.Challenged)
{
// Return custom 401 result
context.Result = new CustomUnauthorizedResult("Authorization failed.");
}
else if (authorizeResult.Forbidden)
{
// Return default 403 result
context.Result = new ForbidResult(Policy.AuthenticationSchemes.ToArray());
}
}
}
public class CustomUnauthorizedResult : JsonResult
{
public CustomUnauthorizedResult(string message)
: base(new CustomError(message))
{
StatusCode = StatusCodes.Status401Unauthorized;
}
}
public class CustomError
{
public string Error { get; }
public CustomError(string message)
{
Error = message;
}
}
The code in this article does exactly what you want. click here
can it return a value such as json "{status:false,code:"401"}". ?
Sure, you can.
[ApiController]
[Produces("application/json")]
public class TestController : ControllerBase
{
public IActionResult Get()
{
if (User.Identity.IsAuthenticated)
{
return new OkObjectResult(new { status: true, code: 200 });
}
return new OkObjectResult(new { status: false, code: 401 });
}
}
But notice that, the request will return with the real status code 200 (OK)
You can also use UnauthorizedObjectResult like #vivek's comment:
return new UnauthorizedObjectResult(new { status: false, code: 401 });
You can return the below if using Asp.Net Core 3.1, It returns UnauthorizedObjectResult.
return Unauthorized(new { status: false, code: 401 });
Currently we are exploring the use of AspNetBoilerPlate (.NETCore and Agular) and one of the biggest pieces we need to be able to use is Azure Active Directory B2C. After trying various methods to get the login page to redirect over to the Azure B2C instance I have had no luck. My goal it so not need to override much if any of the base ABP code in order to accomplish this. In addition, I have what is recommended in the documentation with no success.
https://aspnetboilerplate.com/Pages/Documents/Zero/User-Management?searchKey=authentication#external-authentication
Sorry here are the pieces of code that I have with certain details removed:
Within the Portal.Web.Core project - Authentication I created a new class inheriting from ExternalAuthProviderApiBase and just hard-coded some values for the user. I still need to figure out how to get these from Azure AD B2C.
public class AzureActiveDirectoryB2CAuthProvider : ExternalAuthProviderApiBase
{
public const string Name = "AzureB2C";
public AzureActiveDirectoryB2CAuthProvider()
{
}
public override Task<ExternalAuthUserInfo> GetUserInfo(string accessCode)
{
ExternalAuthUserInfo user = new ExternalAuthUserInfo();
user.EmailAddress = "admin#aol.com";
user.Name = "Administrator";
user.Provider = this.ProviderInfo.Name;
user.ProviderKey = "12345";
user.Surname = "Cool Dude";
return Task.FromResult(user);
}
}
In the Portal.Web.Host --> Startup.cs I register the ExternalAuthProvider in the Configure method just after the app.UserAuthentication
var externalAuthConfiguration = app.ApplicationServices.GetRequiredService<ExternalAuthConfiguration>();
externalAuthConfiguration.Providers.Add(
new ExternalLoginProviderInfo(
AzureActiveDirectoryB2CAuthProvider.Name, string.Empty, string.Empty,
typeof(AzureActiveDirectoryB2CAuthProvider)
)
);
Now on the Angular2 side, I have added a reference to the MSAL.js library created by Microsoft to help handle the authentication.
Within the auth-route-guard.ts file I changed the canActivate method to call the login service which will try to determine if the user is already authenticated and if not redirect them to the Microsoft login page.
if (!this._sessionService.user) {
//this._router.navigate(['/account/login']);
this._loginService.updateUser();
return false;
}
Below you will see the code from the modified login.service.ts file minus the Azure AD B2C details.
import { Injectable } from '#angular/core';
import { Router } from '#angular/router';
import { TokenAuthServiceProxy, AuthenticateModel, AuthenticateResultModel, ExternalLoginProviderInfoModel, ExternalAuthenticateModel, ExternalAuthenticateResultModel } fro`enter code here`m '#shared/service-proxies/service-proxies';
import { UrlHelper } from '#shared/helpers/UrlHelper';
import { AppConsts } from '#shared/AppConsts';
import { MessageService } from '#abp/message/message.service';
import { LogService } from '#abp/log/log.service';
import { TokenService } from '#abp/auth/token.service';
import { UtilsService } from '#abp/utils/utils.service';
declare var Msal: any;
#Injectable()
export class LoginService {
static readonly twoFactorRememberClientTokenName = 'TwoFactorRememberClientToken';
private clientApplication: any;
authenticateModel: AuthenticateModel;
authenticateResult: AuthenticateResultModel;
externalAuthenticateModel: ExternalAuthenticateModel;
externalAuthenticateResult: ExternalAuthenticateResultModel;
rememberMe: boolean;
B2CTodoAccessTokenKey = "msal.idtoken";
tenantConfig = {
tenant: "",
clientID: '',
signUpSignInPolicy: "",
b2cScopes: ["openid"]
};
// Configure the authority for Azure AD B2C
authority = "https://login.microsoftonline.com/tfp/" + this.tenantConfig.tenant + "/" + this.tenantConfig.signUpSignInPolicy;
/*
* B2C SignIn SignUp Policy Configuration
*/
//clientApplication = new Msal.UserAgentApplication(
// this.tenantConfig.clientID, this.authority,
// function (errorDesc: any, token: any, error: any, tokenType: any) {
// // Called after loginRedirect or acquireTokenPopup
// }
//);
constructor(
private _tokenAuthService: TokenAuthServiceProxy,
private _router: Router,
private _utilsService: UtilsService,
private _messageService: MessageService,
private _tokenService: TokenService,
private _logService: LogService,
) {
this.clear();
this.clientApplication =
new Msal.UserAgentApplication(
this.tenantConfig.clientID,
this.authority,
this.authCallback);
}
authenticate(finallyCallback?: () => void): void {
finallyCallback = finallyCallback || (() => { });
var model = new ExternalAuthenticateModel;
model.authProvider = "AzureB2C";
model.providerAccessCode = this.getAccessToken();
model.providerKey = "12345";
this._tokenAuthService
.externalAuthenticate(model)
.finally(finallyCallback)
.subscribe((result: ExternalAuthenticateResultModel) => {
this.processExternalAuthenticateResult(result);
});
}
private processAuthenticateResult(authenticateResult: AuthenticateResultModel) {
this.authenticateResult = authenticateResult;
if (authenticateResult.accessToken) {
//Successfully logged in
this.login(authenticateResult.accessToken, authenticateResult.encryptedAccessToken, authenticateResult.expireInSeconds, this.rememberMe);
} else {
//Unexpected result!
this._logService.warn('Unexpected authenticateResult!');
this._router.navigate(['account/login']);
}
}
private processExternalAuthenticateResult(externalAuthenticateResult: ExternalAuthenticateResultModel) {
this.externalAuthenticateResult = externalAuthenticateResult;
alert(externalAuthenticateResult.accessToken);
if (externalAuthenticateResult.accessToken) {
//Successfully logged in
this.login(externalAuthenticateResult.accessToken, externalAuthenticateResult.encryptedAccessToken, externalAuthenticateResult.expireInSeconds, this.rememberMe);
} else {
//Unexpected result!
this._logService.warn('Unexpected externalAuthenticateResult!');
}
}
private login(accessToken: string, encryptedAccessToken: string, expireInSeconds: number, rememberMe?: boolean): void {
var tokenExpireDate = rememberMe ? (new Date(new Date().getTime() + 1000 * expireInSeconds)) : undefined;
this._tokenService.setToken(
accessToken,
tokenExpireDate
);
this._utilsService.setCookieValue(
AppConsts.authorization.encrptedAuthTokenName,
encryptedAccessToken,
tokenExpireDate,
abp.appPath
);
var initialUrl = UrlHelper.initialUrl;
if (initialUrl.indexOf('/login') > 0) {
initialUrl = AppConsts.appBaseUrl;
}
location.href = initialUrl;
}
private clear(): void {
this.authenticateModel = new AuthenticateModel();
this.authenticateModel.rememberClient = false;
this.authenticateResult = null;
this.rememberMe = false;
}
public AzureB2Clogin(): void {
this.isOnline();
this.clientApplication.loginRedirect(this.tenantConfig.b2cScopes);
}
saveAccessTokenToCache(accessToken: string): void {
sessionStorage.setItem(this.B2CTodoAccessTokenKey, accessToken);
};
public isOnline(): boolean {
return this.clientApplication.getUser() != null;
};
public getAccessToken(): string {
alert('AccessToken: ' + sessionStorage.getItem(this.B2CTodoAccessTokenKey));
return sessionStorage.getItem(this.B2CTodoAccessTokenKey);
}
public updateUser(): void {
if (this.isOnline()) {
this.authenticate();
}
else {
this.AzureB2Clogin();
}
}
public getAuthenticationToken(): Promise<string> {
return this.clientApplication.acquireTokenSilent(this.tenantConfig.b2cScopes)
.then(token => {
alert("Got silent access token: " + token);
return token;
}).catch(error => {
alert("Could not silently retrieve token from storage." + error);
return this.clientApplication.acquireTokenPopup(this.tenantConfig.b2cScopes)
.then(token => {
alert("Got popup access token: "+ token);
return token;
}).catch(error => {
alert("Could not retrieve token from popup." + error);
this.clientApplication.acquireTokenRedirect(this.tenantConfig.b2cScopes);
return Promise.resolve("");
});
});
}
private authCallback(errorDesc: any, token: any, error: any, tokenType: any) {
if (token) {
alert("Id token: " + token);
}
else {
alert(error + ":" + errorDesc);
}
this.getAuthenticationToken();
}
}
Finally index.html has the following script references added to it-->
<script src="https://secure.aadcdn.microsoftonline-p.com/lib/0.1.3/js/msal.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bluebird/3.3.4/bluebird.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fetch/2.0.3/fetch.min.js"></script>
I am trying to load data from my web api controller.
Currently I am using my API service which I call from the ngOnInit function of the component.
But, nothing return in the view because it's an asynchronous data
Web api controller
[HttpGet("[action]")]
public async Task<UserModel> GetUserById(int id)
{
Response.StatusCode = 200;
try
{
_context = new AuthentificationDbContext();
UserModel user = await _context.User.SingleOrDefaultAsync(m => m.id == id);
if (user == null)
{
return null;
}
else
return (user);
}
catch (SqlException ex)
{
throw ex;
}
}
userService.ts
getUserId(id: number) : Observable<User>{
return this.http.get(this.url + 'userApi/GetUserById/?id=' + id)
.map(res => <User>res.json())
.catch(this.handleError);
}
app.routing.ts
{ path: 'user/:id', component: UserComponent}
export const routing = RouterModule.forRoot(appRoutes,{
enableTracing:true});
export const routedComponents = [UserComponent];
user.component.ts
export class UserComponent implements OnInit {
private user: User;
constructor(private userService: UserService, private route: ActivatedRoute, private router: Router) { }
ngOnInit() {
this.route.paramMap
.switchMap((params: ParamMap) =>
this.userService.getUserId(+params.get('id')))
.subscribe((user: User) => {
this.user = user;
});
}
}
user.cshtml
<div *ngIf="user">{{ user.name}}</div>
But, when I tried with that example, that's work because not asynchronous
import { Injectable, Inject } from '#angular/core';
import { Http, Response, RequestOptions, Headers } from '#angular/http';
import { Observable } from 'rxjs/Observable';
export class User {
constructor(public id: number, public name: string) { }
}
let Users = [
new User(11, 'Mr. Nice'),
new User(12, 'Narco')
];
let usersPromise = Promise.resolve(Users);
#Injectable()
export class UserService {
constructor( #Inject(Http) public http: Http) { }
getUserId(id: number | string) {
return usersPromise
.then(users => users.find(user => user.id === +id));
}
}
My question : how to load async data in ngOnInit?
I used by promise also, but doesn't work
If you use
{{user.view}}
in the components template, you'll get an error, because user isn't available immediately.
{{user?.view}}
(safe-nativation operator) avoids this error by not throwing an exception when user is null.
I can resolve my problem (it's related to routing) :
my code just need to insert this :
<script>document.write('<base href="' + document.location + '" />');</script>
at the top of the 'head' section.
And to insert in constructor from app.component.ts, this methode:
click() {
this.router.navigate(['', { foo: 'bar' }]);
}
There is problem with Session in Service, Session is null on second call (solved, see bottom of the post).
I have self-hosted server and client that makes calls to server via JsonServiceClient and ProtoBufServiceClient.
On start of client application I call:
var baseUrl = ConfigGlobal.Host ;
var client = new JsonServiceClient(baseUrl);
var authResponse = client.Post<AuthResponse>("/auth", new Auth
{
UserName = "test1",
Password = "password",
RememberMe = true
});
It works - OnAuthenticated it's fired in my CustomUserSession : AuthUserSession.
authService.SaveSession(session);
didn't help.
Then in one class:
var client = new ProtoBufServiceClient(ConfigGlobal.Host);
client.Put(new ExcelInitialize {Filename = ""}); // OK
Model = client.Get(...); // Session is null
There is a problem in service class in Get method Session is null. If I implement
public CustomUserSession CustomUserSession
{
get
{
return SessionAs<CustomUserSession>();
}
}
I'll get: Only ASP.NET Requests accessible via Singletons are supported.
My AppHost.cs
container.Register<ICacheClient>(new MemoryCacheClient());
container.Register<ISessionFactory>(c => new SessionFactory(c.Resolve<ICacheClient>()));
Plugins.Add(new AuthFeature(
() => new CustomUserSession(), new IAuthProvider[]
{
new CustomCredentialsAuthProvider(),
new BasicAuthProvider(),
}));
Plugins.Add(new RegistrationFeature());
Goal:
Send some variables from client and remember them on host until user logs off.
Edit:
My Workflow looks like this:
SomeClass1:
new auth service client->Post(new Auth(...)); // login ok
nothing more
SomeClass2:
new service client->Put(new E); // some init.
service client->Get(new G);
Service G:
on G request new service client TryResolve();
client->Get(new W)
Service E
on E request CustomUserSession accessible
on W request CustomUserSession not accessible.
My Custom* classes looks like in Scott answer.
Edit:
Here is the code of my problem ready to copy&paste:
private static void Main(string[] args)
{
// Very basic console host
var appHost = new AppHost();
appHost.Init();
appHost.Start("http://*:8082/");
var url = "http://localhost:8082";
var foo = new TestApp.SomeClass1(url);
var bar = new TestApp.SomeClass2(url);
Console.ReadKey();
}
public class AppService : Service
{
public CustomUserSession CustomUserSession
{
get
{
// Returns the typed session
return SessionAs<CustomUserSession>();
}
}
}
public class GService : AppService
{
public object Get(GRequest request)
{
var client = base.TryResolve<EService>();
client.Get(new WRequest());
return new { CustomUserSession.SuperHeroIdentity };
}
}
public class EService : AppService
{
public void Get(WRequest wRequest)
{
Console.WriteLine(CustomUserSession.SuperHeroIdentity);
}
public void Get(ERequest request)
{
Console.WriteLine(CustomUserSession.SuperHeroIdentity);
}
public void Put(ERequest request)
{
Console.WriteLine(CustomUserSession.SuperHeroIdentity);
}
}
public class SomeClass1
{
public SomeClass1(string url)
{
var client = new JsonServiceClient(url);
client.Post<AuthResponse>("/auth", new Auth
{
UserName = "clark.kent",
Password = "kryptonite",
RememberMe = true
});
}
}
public class SomeClass2
{
public SomeClass2(string url)
{
var client = new JsonServiceClient(url);
client.Put(new ERequest());
client.Get(new GRequest());
}
}
public class GRequest : IReturnVoid
{
}
public class ERequest : IReturnVoid
{
}
public class WRequest : IReturnVoid
{
}
Solution (for this problem):
Save session cookies in client application and restore them before every call to Webservice.
Use Service::Resolve() instead of Service::TryResolve
The code you have posted looks okay to me. So it's likely something trivial with your setup.
I have created a simple, self hosted app which also uses a CustomUserSession and a CustomCredentialsAuthProvider, hopefully using this as a guide will highlight what is going wrong. Let me know how you get on.
using ServiceStack.CacheAccess;
using ServiceStack.CacheAccess.Providers;
using ServiceStack.ServiceInterface;
using ServiceStack.ServiceInterface.Auth;
using ServiceStack.ServiceHost;
using ServiceStack.WebHost.Endpoints;
namespace Testv3
{
class MainClass
{
public static void Main()
{
// Very basic console host
var appHost = new AppHost();
appHost.Init();
appHost.Start("http://*:8082/");
Console.ReadKey();
}
}
public class AppHost : AppHostHttpListenerBase
{
public AppHost() : base("Test Service", typeof(TestApp).Assembly) {}
public override void Configure(Funq.Container container)
{
// Cache and session IoC
container.Register<ICacheClient>(new MemoryCacheClient());
container.Register<ISessionFactory>(c => new SessionFactory(c.Resolve<ICacheClient>()));
// Register the Auth Feature with the CustomCredentialsAuthProvider.
Plugins.Add(new AuthFeature(
() => new CustomUserSession(),
new IAuthProvider[]
{
new CustomCredentialsAuthProvider(),
new BasicAuthProvider(),
})
);
}
}
public class CustomCredentialsAuthProvider : CredentialsAuthProvider
{
public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
{
// Replace with a database lookup
return (userName == "clark.kent" && password == "kryptonite");
}
public override void OnAuthenticated(IServiceBase authService, IAuthSession session, IOAuthTokens tokens, Dictionary<string, string> authInfo)
{
var customSession = session as CustomUserSession;
if(customSession != null)
{
// Replace these static values with a database lookup
customSession.FirstName = "Clark";
customSession.LastName = "Kent";
customSession.SuperHeroIdentity = "Superman";
}
authService.SaveSession(customSession, SessionExpiry);
}
}
public class CustomUserSession : AuthUserSession
{
// Our added session property
public string SuperHeroIdentity { get; set; }
}
public static class TestApp
{
[Route("/SuperHeroTime", "GET")]
public class SuperHeroTimeRequest {}
public class TestController : Service
{
public CustomUserSession CustomUserSession
{
get
{
// Returns the typed session
return SessionAs<CustomUserSession>();
}
}
[Authenticate]
public object Get(SuperHeroTimeRequest request)
{
// Return the result object
return new { CustomUserSession.FirstName, CustomUserSession.LastName, Time = DateTime.Now.ToString(), CustomUserSession.SuperHeroIdentity };
}
}
}
}
If you put this index.html in your bin folder and navigate to http://localhost:8082/index.html you can call the service, to test it.
<!doctype html>
<html>
<head>
<title>Test</title>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script>
function login()
{
$.ajax({
type: "POST",
url: "/auth/credentials",
contentType: "application/json",
data: JSON.stringify({
UserName: "clark.kent",
Password: "kryptonite",
RememberMe: true
})
}).done(function(result){
getSuperHeroTime();
});
}
function getSuperHeroTime()
{
$.ajax({
type: "GET",
url: "/SuperHeroTime",
contentType: "application/json",
}).done(function(result){
$("#result").html(result.FirstName + " " +
result.LastName + " is " +
result.SuperHeroIdentity + " (" +
result.Time + ")");
});
}
</script>
</head>
<body>
<h1>Super Hero Time</h1>
<button onclick="login()">Go</button>
<div id="result"></div>
</body>
</html>
Update:
Having looked at your usage code provided in your edit. You are calling the Auth method in SomeClass1 and then that JsonServiceClient isn't reused. So your session won't follow. You have to reuse the client, because the client stores the cookies that track your session.
In SomeClass2 you are effectively calling the service without authenticating, so the session is null. You need to reuse the same JsonServiceClient for your requests.
You could run your authentication method and then pass the session cookies to any subsequent JsonServiceClient, to save re-authenticating with each client. It's easy to do:
var authClient = new JsonServiceClient(url);
authclient.Post<AuthResponse>("/auth", new Auth {
UserName = "clark.kent",
Password = "kryptonite",
RememberMe = true
});
// Get the session cookies
var cookies = authClient.CookieContainer.GetCookies(new Uri(url));
// In your other `JsonServiceClient`s set the cookies before making requests
var anotherClient = new JsonServiceClient(url);
anotherClient.CookiesCollection.add(cookies);
anotherClient.Post( ...
You are also trying to resolve another service within your service using:
base.TryResolve<EService>();
You need to use this instead, or you will see the Only ASP.NET Requests accessible via Singletons are supported exception:
base.ResolveService<EService>();