How to call from one authorized service to another service that requires authorization - servicestack

I have an Item Service:
[Authenticate]
public class ItemService : ServiceStack.Service {
//implementation
}
Within the ItemService I am debugging this Get method that has received a valid Token and is able to successfully create session:
public GetItemResponse Get(GetItem request)
{
var session = SessionAs<CustomUserSession>();
var authToks1 = session.GetAuthTokens();//empty
var authToks2 = session.GetAuthTokens(_authServiceConnection);//null
var authService = new JsonServiceClient(_authServiceConnection);
//not authorized
ConvertSessionToTokenResponse attempt1 = authService.Send(new ConvertSessionToToken());
//not authorized
ConvertSessionToTokenResponse attempt2 = authService.Send(new ConvertSessionToToken() { PreserveSession = true });
var accountService = new JsonServiceClient(_accountServiceConnection)
{
BearerToken = "what to do",
RefreshToken = "what to do"
};
return new GetItemResponse();
}
Obviously I am simply trying to call another service, AccountService:
[Authenticate]
public class AccountService : ServiceStack.Service {
//implementation
}
How to include a JWT Refresh token or JWT Bearer token in a request from one authorized service to another service that has the ServiceStack Authenticate attribute.
Note I am using a custom Auth provider, I have a legacy database.

If this another ServiceStack Service with the same Host you should use the Service Gateway as internal requests are executed in process directly which avoid the request filter validation.
var response = Gateway.Send(new MyRequest());
If you're trying to call a remote ServiceStack Service you can get the JWT Token sent with the request with IRequest.GetJwtToken() extension method and forward it to the downstream Service requests:
var accountService = new JsonServiceClient(_accountServiceConnection)
{
BearerToken = Request.GetJwtToken(),
};

Related

What is the right way to pass request data to services in nestjs?

I have many services that all need to know the tenant ID from the request (kept in JWT auth token). The request is either GRPC (jwt stored in MetaData) or Graphql (jwt stored in context.headers.authorization).
I would like to be able to force myself not to forget to pass this tenant id when using the services. Ideally I dont want to even have to constantly write the same code to get the info from the request and pass it through. However the only ways I've managed to do it was using:
#Inject(REQUEST) for grpc in the service constructor. This doesn't work for the graphql requests. The only other way I saw was to only return service methods AFTER providing the data, which looks ugly as hell:
class MyService {
private _actions: {
myMethod1() { ... }
}
withTenantDetails(details) {
this._details = details;
return this._actions;
}
}
If I can somehow get the execution context within MyService that would be a good option, and make this easy using:
const getTenantId = (context: ExecutionContext) => {
if (context.getType() === 'rpc') {
logger.debug('received rpc request');
const request = context.switchToRpc().getContext();
const token = request.context.get("x-authorization");
return {
token,
id: parseTokenTenantInfo(token)
};
}
else if (context.getType<GqlContextType>() === 'graphql') {
logger.debug('received graphql request');
const gqlContext = GqlExecutionContext.create(context);
const request = gqlContext.getContext().request;
const token = request.get('Authorization');
return {
token,
id: parseTokenTenantInfo(token)
};
}
else {
throw new Error(`Unknown context type receiving in tenant param decorator`)
}
}
But I can't find any way to get the executioncontext across to the service without also having to remember to pass it every time.
It's possible to inject Request into injectable service.
For that, the Service will be Scope.Request, and no more Singleton, so a new instance will be created for each request. It's an important consideration, to avoid creating too many resources for performance reason.
It's possible to explicit this scope with :
#Injectable({ scope: Scope.REQUEST })
app.service.ts :
#Injectable({ scope: Scope.REQUEST })
export class AppService {
tenantId: string;
constructor(#Inject(REQUEST) private request: Request) {
// because of #Inject(REQUEST),
// this service becomes REQUEST SCOPED
// and no more SINGLETON
// so this will be executed for each request
this.tenantId = getTenantIdFromRequest(this.request);
}
getData(): Data {
// some logic here
return {
tenantId: this.tenantId,
//...
};
}
}
// this is for example...
const getTenantIdFromRequest = (request: Request): string => {
return request?.header('tenant_id');
};
Note that, instead of decode a JWT token in order to retrieve TENANT_ID for each request, and maybe for other service (one per service), an other approach could be to decode JWT one single time, and then add it in Request object.
It could be done with a global Guard, same as authorization guard examples of official docs.
Here just a simple example : (could be merged with a Auth Guard)
#Injectable()
export class TenantIdGuard implements CanActivate {
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
const request = context.switchToHttp().getRequest();
request['tenantId'] = getTenantIdFromRequest(request);
return true; // or any other validation
}
}
For GraphQL applications, we should inject CONTEXT in place of REQUEST :
constructor(#Inject(CONTEXT) private context) {}
You have to set either request inside context, or directly TENANT_ID inside context in order to retrieve it after inside service.

How to put an Api key in the Authenticate message?

I'm trying to combine the api key auth provider with the encrypted messaging plugin.
var client = new JsonServiceClient(home);
client.BearerToken = "somesecret";
works
but i want my apikey to be in the message so i tried
var authResponse = client.Post(new Authenticate
{
provider = ApiKeyAuthProvider.Name,
UserName = "somesecret"
});
This post fails at runtime with a 401 not authenticated.
How do i get this to work?
IAuthWithRequest Auth Providers like the API Key Auth Provider needs to be sent per request with the Authenticated User Session it establishes only lasts for the lifetime of that request. It can't be used with the Authenticate Service to Authenticate the client as your example tried to do, it must be included in each request to an Authenticated Service.
The normal way to call a protected Service with the API Key is to just populate the BearerToken property:
var client = new JsonServiceClient(baseUrl) {
BearerToken = apiKey
};
Which will then let you call your [Authenticate] Service:
var response = client.Get(new Secure { Name = "World" });
Encrypted Messaging Support
Previously you could only embed the User SessionId within an Encrypted Messaging Request but I've just added support for Authenticating Encrypted Messaging Services with a BearerToken in this commit which works similar to populating a SessionId, where you can now populate a BearerToken as used in API Key and JWT Auth Providers by having your Request DTOs implement IHasBearerToken, e.g:
public class Secure : IHasBearerToken
{
public string BearerToken { get; set; }
public string Name { get; set; }
}
This will let you embed the BearerToken when calling the protected Service, e.g:
IEncryptedClient encryptedClient = client.GetEncryptedClient(publicKey);
var response = encryptedClient.Get(new Secure { BearerToken = apiKey, Name = "World" });
Where it will be embedded and encrypted along with all content in the Request DTO.
Alternatively you can also set the BearerToken property on the IEncryptedClient once and it will automatically populate it on all Request DTOs that implement IHasBearerToken, e.g:
encryptedClient.BearerToken = apiKey;
var response = encryptedClient.Get(new Secure { Name = "World" });
The new BearerToken support in Encrypted Messaging is available from v5.1.1 that's now available on MyGet.

ServiceStack MQ: how to populate data in RequestContext

I'm developing a JWT-based multi-tenancy system using ServiceStack. The JWT token contains shard information, and I use JwtAuthProvider to translate the JWT token to session object following instructions at http://docs.servicestack.net/jwt-authprovider.
Now, I want to use ServiceStack MQ for asynchronous processing. The MQ request needs to be aware of the shard information, so I populate the request context before executing it as follow
mqServer.RegisterHandler<EmployeeAssignedToProject>(m =>
{
var req = new BasicRequest { Verb = HttpMethods.Post };
var sessionKey = SessionFeature.GetSessionKey(m.GetBody().SessionId);
var session = HostContext.TryResolve<ICacheClient>().Get<Context>(sessionKey);
req.Items[Keywords.Session] = session;
var response = ExecuteMessage(m, req);
return response;
});
Here, Context is my custom session class. This technique is stemmed from the instruction at http://docs.servicestack.net/messaging#authenticated-requests-via-mq. Since I execute the message within the context of req, I reckon that I should then be able to resolve Context as follow
container.AddScoped<Context>(c =>
{
var webRequest = HostContext.TryGetCurrentRequest();
if (webRequest != null)
{
return webRequest.SessionAs<Context>();
} else
{
return HostContext.RequestContext.Items[Keywords.Session] as Context;
}
});
However, HostContext.RequestContext.Items is always empty. So the question is, how to populate HostContext.RequestContext.Items from within message handler registration code?
I've tried to dig a little bit into ServiceStack code and found that the ExecuteMessage(IMessage dto, IRequest req) in ServiceController doesn't seem to populate data in RequestContext. For my case, it is a bit too late to get session inside service instance, as a service instance depends on some DB connections whose shard info is kept in session.
The same Request Context instance can't be resolved from the IOC. The Request Context instance is created in the MQ's RegisterHandler<T>() where you can add custom data in the IRequest.Items property, e.g:
mqServer.RegisterHandler<EmployeeAssignedToProject>(m =>
{
var req = new BasicRequest { Verb = HttpMethods.Post };
req.Items[MyKey] = MyValue; //Inject custom per-request data
//...
var response = ExecuteMessage(m, req);
return response;
});
This IRequest instance is available throughout the Request pipeline and from base.Request in your Services. It's not available from your IOC registrations so you will need to pass it in as an argument when calling your dependency, e.g:
public class MyServices : Service
{
public IDependency MyDep { get; set; }
public object Any(MyRequest request) => MyDep.Method(base.Request, request.Id);
}

Failed to get AccessToken via authorization code using MSAL 1.1.0-preview in asp.net core

I followed official steps as below to try the scenario "web app calling a Web API in Azure Ad B2C", the only difference is I am using Asp.Net core. I am using AuthorizationCode to get the access token, but it always returns with id token and NULL access token.
Create an Azure AD B2C tenant.
Register a web api.
Register a web app.
Set up policies.
Grant the web app permissions to use the web api.
My code:
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
AuthenticationScheme = OpenIdConnectDefaults.AuthenticationScheme,
AutomaticChallenge = true,
ClientId = aadB2cSettings.ClientId,
MetadataAddress = $"{aadB2cSettings.Instance}{aadB2cSettings.Tenant}/v2.0/.well-known/openid-configuration?p={aadB2cSettings.B2cSignUpOrSignInPolicy}",
PostLogoutRedirectUri = aadB2cSettings.RedirectUrl,
ResponseType = OpenIdConnectResponseType.CodeIdToken,
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name"
},
Events = new OpenIdConnectEvents
{
OnAuthorizationCodeReceived = async context =>
{
var authCode = context.TokenEndpointRequest.Code;
var b2cAuthority = $"{aadB2cSettings.Instance}tfp/{aadB2cSettings.Tenant}/{aadB2cSettings.B2cSignUpOrSignInPolicy}/v2.0/.well-known/openid-configuration";
var cca = new ConfidentialClientApplication(
aadB2cSettings.ClientId,
b2cAuthority,
aadB2cSettings.RedirectUrl,
new ClientCredential(aadB2cSettings.ClientSecret),
new TokenCache(),
null);
try
{
var authResult = await cca.AcquireTokenByAuthorizationCodeAsync(authCode, new[] { "https://hulab2c.onmicrosoft.com/b2cdemo/all" });
context.HandleCodeRedemption(authResult.AccessToken, authResult.IdToken);
}
catch (Exception ex)
{
throw ex;
}
}
},
Used fiddler to capture the request, it is:
POST
https://login.microsoftonline.com/hulab2c.onmicrosoft.com/oauth2/v2.0/token?p=b2c_1_signuporsignin
HTTP/1.1
Request Body:
client_id=1ff91f47-08ee-4973-83f4-379ad7e0679c&client_info=1&client_secret=......&scope=https%3A%2F%2Fhulab2c.onmicrosoft.com%2Fb2cdemo%2Fall+offline_access+openid+profile&grant_type=authorization_code&code=......&redirect_uri=https%3A%2F%2Flocalhost%3A44383%2F
Return:
{"id_token":"......","token_type":"Bearer","not_before":1494494423,"client_info":"......","scope":""}
So only id token, no access token. But we should get access token here, right?
Finally found out my failure reason: the request to get AuthorizationCode doesn't contain the target scope. Reflect in code, for OpenIdConnectOption in aspnetcore, the Scope parameter is readonly and its default value is "opened profile".
Scope is readonly in OpenIdConnectOption
So the default authorization code request sent is:
GET
https://login.microsoftonline.com/hulab2c.onmicrosoft.com/oauth2/v2.0/authorize?p=b2c_1_signuporsignin&client_id=7f865ca0-271e-4f27-be21-6f0072fe3ad7&redirect_uri=https%3A%2F%2Flocalhost%3A44355%2Fsignin-oidc&response_type=code%20id_token&scope=openid%20profile&response_mode=form_post&nonce=......
HTTP/1.1
Thus, using this authorization code in response to get token, even we set right scope in the token request, we still can't get the access code but only id token, because the provide authorization code is only for "openid profile".
To fix this, we need to add target web api scope into the authorization code as well. Here is the how-to-fix code:
Events = new OpenIdConnectEvents
{
OnRedirectToIdentityProvider = context =>
{
context.ProtocolMessage.Scope += $" offline_access {myapiscope}";
return Task.FromResult(0);
},
......
}
In AspNet, we don't need to do this because its scope is not readonly as aspnetcore and can be set directly:
new OpenIdConnectAuthenticationOptions
{
......
Scope = $"openid profile offline_access {ReadTasksScope} {WriteTasksScope}"
}
https://github.com/Azure-Samples/active-directory-b2c-dotnet-webapp-and-webapi/issues/4 Microsoft have reproduced the issue and working on fix

ServiceStack Ws-Security Auth Provider

I'm trying to figure out how to support ws-security as authentication mechanism in SS.
My goal is to have all DTO handled in json,xml,saop11,saop12(that part has been achieved following the SS documentation) and supporting multiple auth providers including one based on ws-security.
DTOs should not be affected at all by the authentication mechanism.
In case the DTO will be sent using saop12, the soap message will be the the call sample generated by the metadata endpoint(soap envelope + soap body) plus a soap header including the ws-security element for the WS-Security Username Authentication. A dedidcated "soap auth provider" should inspect the message, use the soap header -> security element and perform the authentication.
Along with the soap auth provider, I may have other built-in auth mechanism that may used for json message and/or other formats.
Exists a SS auth provider based on ws-security that I'm not aware of?
Any guidelines, suggestions, thoughts to implement it?
At the present than my solution
//AppHost
Plugins.Add(new AuthFeature(() => new CustomAuthUserSession(),
new IAuthProvider[] {
new CustomCredentialsAuthProvider(),
new SoapMessageAuthProvider(),
}
));
// required by the SoapMessageAuthProvider to inspect the message body serching for ws-security element
PreRequestFilters.Add((httpReq, httpRes) =>
{
httpReq.UseBufferedStream = false;
});
I based the SoapMessageAuthProvider on the built-in BasicAuthProvider.
Since the SoapMessageAuthProvider requires to inspect the incoming message on each call serching for ws-security element, I implemented IAuthWithRequest
public void PreAuthenticate(IRequest req, IResponse res)
{
//Need to run SessionFeature filter since its not executed before this attribute (Priority -100)
SessionFeature.AddSessionIdToRequestFilter(req, res, null);
var userPass = ExtractSoapMessageUserNameCredentials(req);//req.GetBasicAuthUserAndPassword();
if (userPass != null)
{
var authService = req.TryResolve<AuthenticateService>();
//var response = authService.Post(new Authenticate
//{
// provider = Name,
// UserName = userPass.Value.Key,
// Password = userPass.Value.Value
//});
authService.Request = req;
var session = authService.GetSession(false);
var userName = userPass.Value.Key;
//Add here your custom auth logic (database calls etc)
var userAuth = new UserAuth();
userAuth.Id = 10;
userAuth.UserName = userName;
var holdSessionId = session.Id;
session.PopulateWith(userAuth); //overwrites session.Id
session.Id = holdSessionId;
session.IsAuthenticated = true;
session.UserAuthId = userAuth.Id.ToString(CultureInfo.InvariantCulture);
session.UserAuthName = userName;
}
}
//called by CustomAuthUserSession.IsAuthorized
// to be reviewed to keep isolated from other providers
public override bool IsAuthorized(IAuthSession session, IAuthTokens tokens, Authenticate request = null)
{
if (request != null)
{
if (!LoginMatchesSession(session, request.UserName))
{
return false;
}
}
return !session.UserAuthId.IsNullOrEmpty();//filled by PreAuthenticate
}
the custom session calls each provider, including the SoapMessageAuthProvider that meanwhile, through the PreAuthenticate method, filled out the session with authenticated user data.
public class CustomAuthUserSession : AuthUserSession
{
public override bool IsAuthorized(string provider)
{
var tokens = ProviderOAuthAccess.FirstOrDefault(x => x.Provider == provider);
return AuthenticateService.GetAuthProvider(provider).IsAuthorizedSafe(this, tokens);
}
...
}
I need to make sure the soap provider will be always invoked for soap message w/ ws-security and the call should not be authenticated by other providers:
- user get authentication through the CustomCredentialsAuthProvider(cookie based)
- user call the service supply json message within the web request that carries the auth cookie
- a further call sends a soap message carrying the same auth cookie: since the message is in soap format and includs the soap header ws-security, the call should be authenticated only using the soap provider using the soap header ws-security within the message.
I understand that a weird scenario, but I'm trying to understand how to accomplish it.
My guess that happends through the ServiceStack.AuthenticateAttribute line 72
matchingOAuthConfigs.OfType<IAuthWithRequest>()
.Each(x => x.PreAuthenticate(req, res));

Resources