I have two ServiceStack servers X and Y. Server X has functionality to register and authenticate users. It has RegistrationFeature,CredentialsAuthProvider, MemoryCacheClient and MongoDbAuthRepository features to handle the authentication.
Recently, I introduced server Y and GUI forms that talk to server Y to handle another part of my business domain. Server Y needs to make requests to authenticated endpoints on server X.
How do I configure server Y in such a way that when it gets login requests from the GUI forms, it passes that responsibility to Server X which has access to the user information?
I tried implementing a custom CredentialsAuthProvider in server Y like so:
public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
{
// authenticate through server X
try
{
var client = new JsonServiceClient("http://localhost:8088");
var createRequest = new Authenticate
{
UserName = userName,
Password = password,
provider = Name,
};
var authResponse = client.Post(createRequest);
return true;
}
catch (WebServiceException ex)
{
// "Unauthorized
return false;
}
}
but later when I try to make a request from a service in server Y to an authenticated endpoint in server X, I get Unauthorized error.
public class MyServices2 : Service
{
public object Any(TwoPhase request)
{
try
{
// make a request to server X on an authenticated endpoint
var client = new JsonServiceClient("http://localhost:8088");
var helloRequest = new Hello
{
Name = "user of server Y"
};
var response = client.Post(helloRequest);
return new TwoPhaseResponse { Result = $"Server X says: {response.Result}" };
}
catch (WebServiceException e)
{
Console.WriteLine(e);
throw;
}
}
...
}
This is highly dependent on the method of Authentication you choose. If you want to use CredentialsAuthProvider than you must ensure each Server is configured to use the same distributed Caching Provider instance (i.e. any Caching Provider other than MemoryCacheClient). This is because when you're authenticated, the Session Cookie Ids which point to an Authenticated User Session are populated on the Service Client which is sent with each Request. The ServiceStack Instance that receives the Session Cookie Ids would use it to access the Authenticated User Session in the registered caching provider.
If both ServiceStack Services are configured to use the same Caching Provider you could transfer the Session Cookie from the incoming Request to a new Service Client with something like:
Transferring Session Id
public object Any(ClientRequest request)
{
// make a request to server X on an authenticated endpoint
var session = base.SessionAs<AuthUserSession>();
var client = new JsonServiceClient("http://localhost:8088");
client.SetSessionId(session.Id);
var response = client.Post(new Hello {
Name = "user of server Y"
});
return new TwoPhaseResponse { Result = $"Server X says: {response.Result}" };
}
Transferring BasicAuthProvider Credentials
Otherwise if you're using HTTP Basic Auth with the BasicAuthProvider then the UserName/Password is sent with the Request which you can transfer to your internal Service Client with:
var basicAuth = base.Request.GetBasicAuthUserAndPassword();
client.UserName = basicAuth.Value.Key;
client.Password = basicAuth.Value.Value;
client.AlwaysSendBasicAuthHeader = true;
Which will copy the UserName/Password sent on the incoming request and send it with the outgoing Request. But for this to work both ServiceStack Instances must be configured to use the same BasicAuthProvider and User Auth Repository since the downstream Server needs to be able to validate the UserName/Password provided.
Transferring API Key
Likewise you can use the API Key AuthProvider to do something similar but instead of forwarding UserName/Password you can forward an API Key with:
var apikey = base.Request.GetApiKey();
client.BearerToken = apikey.Id;
Again this will need to be configured with the Same ApiKeyAuthProvider and User Auth Repository as the downstream server will require validating the API Key provided.
Using JWT AuthProvider for Stateless Authentication
Otherwise if you don't want each Server to share the same infrastructure dependencies (e.g. Caching Provider / User Auth Repository) I'd look at consider using the JWT Auth Provider which is ideal for this scenarios where Authenticating with one ServiceStack Instance that issues the the JWT Token encapsulates the Users Session and lets you make authenticated Requests to other ServiceStack instances which just need to have a JwtAuthProviderReader registered.
To transfer the JWT Token you can access it with:
var bearerToken = base.Request.GetBearerToken()
?? base.Request.GetCookieValue(Keywords.TokenCookie);
and populate it on the internal Service Client with:
client.BearerToken = bearerToken;
Related
I'm trying to get an API to pass client credentials through to the database (on a different server) but experiencing something that smells very much like a Kerberos double-hop issue to me, however the systems people say that Kerberos delegation is set up appropriately on the servers in question. Below is a method I wrote to test this, and at the bottom is the response body I get in Postman when sending my own credentials to the API via NTLM. I get a similar response when shipping the credentials of another account I have access to. When shipping credentials I know to be bad, IIS stops my request & responds with a 401 without sending the request to the API; this is expected and required.
What can be causing this other than a Kerberos double-hop? Is there a flaw in my implementation of impersonation? I don't have access to either server in question, but I can supply the entire ugly exception encountered by the controller if needed.
/// <summary>
/// This endpoint should not support PUT. This method is simply a means by which to test impersonation
/// </summary>
[HttpPut]
public IActionResult PutMethod()
{
var bogusPutResult = new ImpersonationResult();
if (!(this.User.Identity is WindowsIdentity user)) return Problem("Cannot authenticate user");
var row1 = _myDbContext.MyTable.Find(1);
bogusPutResult.BeforeImpersonation = $"{WindowsIdentity.GetCurrent().Name} got a row with value {row1.Value}";
WindowsIdentity.RunImpersonated(user.AccessToken, () =>
{
string message;
try
{
var row2 = _myDbContext.MyTable.Find(2); // All domain users have SELECT access
message = $" got a row with value {row2.Value}";
}
catch (Exception e)
{
message = $" ate this: {e.GetType()}--{e.Message}";
}
bogusPutResult.DuringImpersonation = $"{WindowsIdentity.GetCurrent().Name} {message}";
});
bogusPutResult.AfterImpersonation = WindowsIdentity.GetCurrent().Name;
return Ok(bogusPutResult);
}
And the JSON returned by the API...
{
"BeforeImpersonation": "MYDOMAIN\\srv-apiserviceaccount got a row with value 34 ",
"DuringImpersonation": "MYDOMAIN\\finglixon ate this: Microsoft.Data.SqlClient.SqlException--Login failed for user 'NT AUTHORITY\\ANONYMOUS LOGON'.",
"AfterImpersonation": "MYDOMAIN\\srv-apiserviceaccount"
}
I have an API and other services configured and secured via AWS. We have applied a RBAC style permission system to allow/deny access to resources using groups in Cognito and federated-identities.
When a user logs into to the system, they get a JWT token listing the cognio:roles they have access to, and can use these to perform different actions by requesting a temporary session token for that resource.
I currently have a Angular website, which is working as expected and once the user has logged in, is able to request the session tokens like this:
buildCognitoCreds(idTokenJwt: string, customRoleArn: string) {
let url = 'cognito-idp.' + CognitoUtil._REGION.toLowerCase() + '.amazonaws.com/' + CognitoUtil._USER_POOL_ID;
let logins: CognitoIdentity.LoginsMap = {};
logins[url] = idTokenJwt;
let params = {
IdentityPoolId: CognitoUtil._IDENTITY_POOL_ID, /* required */
Logins: logins,
CustomRoleArn: customRoleArn
};
let serviceConfigs: awsservice.ServiceConfigurationOptions = {};
let creds = new AWS.CognitoIdentityCredentials(params, serviceConfigs);
console.log('Creds for role: ' + customRoleArn + ':', creds);
this.setCognitoCreds(creds);
return creds;
}
I am now trying to do the same in .net as we have a number of desktop and mobile (Xamarin) app that need to access the AWS resources / api.
I have followed the AWS blog here: https://aws.amazon.com/blogs/developer/cognitoauthentication-extension-library-developer-preview/
and am able to authenticate my users against my cognito user pool.
I am now stuck, as how to get the session tokens for a specific role the user has permissions for - I have tried using the following:
var credentials = _authenticationService.User.GetCognitoAWSCredentials(
"us-east-1:ef964b45-939e-4ef3-91c6-xxxxxxxxxxxx", // Identity pool ID
RegionEndpoint.USEast1 // Region
);
var url = "cognito-idp.us-east-1.amazonaws.com/us-east-1_xxxxxxxxx";
var logins = new Dictionary<string, string>();
logins.Add(url, _authenticationService.AuthenticationResult.IdToken);
GetCredentialsForIdentityRequest request = new GetCredentialsForIdentityRequest();
request.IdentityId = "us-east-1:ef964b45-939e-4ef3-91c6-xxxxxxxxxxxxx";
request.Logins = logins;
request.CustomRoleArn = customRoleArn;
var c = new AmazonCognitoIdentityClient(credentials, RegionEndpoint.USEast1);
var test = c.GetIdentityPoolRolesAsync("us-east-1:ef964b45-939e-4ef3-91c6-xxxxxxxxxxxxxx").Result;
var result = c.GetCredentialsForIdentityAsync(request).Result;
Console.WriteLine(result.Credentials.AccessKeyId);
However every time I do I get a: Amazon.CognitoIdentity.Model.NotAuthorizedException with the message:
The ambiguous role mapping rules for: cognito-idp.us-east-1.amazonaws.com/us-east-1_xxxxxxxxx denied this request.
From tracing through the code, and testing it seems that I somehow need to specify the CustomRoleArn to use when getting the cognito was credentials from the Cognito User, otherwise token based rule in the cognito federated identity will return a deny response. - But I can't work out how to do this in the AWS .net SDK......
I'm trying to figure out how can I subscribe a just-authenticated user to a SSE channel using the ServiceStack's OnAuthenticated function.
Here is my actual code:
public override void OnAuthenticated(IRequest httpReq, IAuthSession session, IServiceBase authService, IAuthTokens tokens, Dictionary<string, string> authInfo) {
string subscriptionId = // ???;
string[] channels = { "mychan1", "mychan2" };
ServerEvents.SubscribeToChannels(subscriptionId, channels);
}
My question is: how can I bind the subscriptionId to the just-authenticated user in way to give him\her the subscription to the channels?
Thank you very much!
You can't subscribe on the behalf of a user in a Session, the client needs to make their own authenticated Server Events connection using the same Service Client they've authenticated with, e.g:
Initialize ServerEventsClient:
var client = new ServerEventsClient(baseUri, channel=channelName) {
// Register any handlers...
};
Authenticate using the built-in Service Client:
client.ServiceClient.Post(new Authenticate {
provider = "credentials",
UserName = "user",
Password = "pass",
RememberMe = true,
});
Start the ServerEventsClient to establish an Authenticated Server Events connection:
client.Start();
Alternatively see this previous answer for examples of establishing authenticated Server Event connctions with different Auth Providers like JWT.
I have a WebApi that I want to authorize my user with his linkedin information (as in create an access token and inject it in to my owin).
So far I have tried to work with Sparkle.Linkedin and this is what I have
public LinkedInLogic() {
// create a configuration object
_config = new LinkedInApiConfiguration(ApiKey, ApiSecret);
// get the APIs client
_api = new LinkedInApi(_config);
}
public Uri GetAuthUrl() {
var scope = AuthorizationScope.ReadBasicProfile;
var state = Guid.NewGuid().ToString();
var redirectUrl = "http://localhost:1510/api/login/RedirectAuth";
return _api.OAuth2.GetAuthorizationUrl(scope, state, redirectUrl);
}
public void GetAccessToken(string code) {
//If I do api.GetAccessToken(code); here I get an access token
var request = System.Net.WebRequest.Create("http://localhost:1510/api/token?grant_type=authorization_code&code=" + code);
request.GetResponse(); // my owin authorization
}
So I first get the Authorization Url -> it opens a popup -> I enter my data and it goes back to a controller which fires up GetAccessToken.
Problem is even if I completely authorize with linkedin I am not sure how to authorize with my own webapi. So I tried to send an http request to my owin token giver but it doesn't like it. There is also doesn't seem to be anyway I can return the access token back to the user so he can use it in his session.
Any ideas?
Not too sure if the sparkle is working anymore since the changes that where made by Linkedin on May 2015
I need to validate a user against an application with custom UserName and Password. The credentials are compared with those in database and then the user can be authorized.
I configured my AppHost adding the plugin for authentication:
Plugins.Add(new AuthFeature(() => new AuthUserSession(), new IAuthProvider[]{
new CredentialsAuthProvider()
}));
I have decorated the my DTO with [Authenticate] attribute
I then created a service to handle the Authenticate call:
public AuthenticateResponse Any(Authenticate request = null)
{
var response = new AuthenticateResponse();
// code to get user from db
//...
// check if credentials are ok
if (passInDB == request.Password)
{
var session = this.GetSession();
session.IsAuthenticated = true;
session.UserName = userFromDBEntity.Username;
response.UserId = userFromDBEntity.ID.ToString();
}
return response;
}
In the client app I created a call to the service to provides me authentication:
AuthenticateResponse authResponse = client.Post(new Authenticate
{
provider = Axo.WebServiceInterface.AxoAuthProvider.Name, //= credentials
UserName = username,
Password = password,
RememberMe = true
});
Then, still in the client, I have written something like:
if (authResponse.UserId != null)
{
client.AlwaysSendBasicAuthHeader = true;
client.SessionId = authResponse.SessionId;
}
..with the hope to get aware the client that now I am an authenticated user, but after debugging to death I'm still having an UNAUTHORIZED Exception.
I am able to reach the Authenticate Service I created, and check the credentials against the db, but after that it seems the jsonclient needs something more than "SessionId" to know that it is authenticated, because I get the error for any other request. I suppose that headers are missing something.
I read a lot of posts, and I tried also to define my custom AuthProvider and then override TryAuthenticate to see if may be helpful (for someone it was) but the method doesn't even get fired..
There's an example of using ServiceStack's Authentication to implement a Custom Auth Provider by inheriting CredentialsAuthProvider and overriding TryAuthenticate() to determine whether the userName/password is valid and OnAuthenticated() to populate the Users IAuthSession with info from the existing DB:
public class CustomCredentialsAuthProvider : CredentialsAuthProvider
{
public override bool TryAuthenticate(IServiceBase authService,
string userName, string password)
{
//Add here your custom auth logic (database calls etc)
//Return true if credentials are valid, otherwise false
}
public override IHttpResult OnAuthenticated(IServiceBase authService,
IAuthSession session, IAuthTokens tokens,
Dictionary<string, string> authInfo)
{
//Fill IAuthSession with data you want to retrieve in the app eg:
session.FirstName = "some_firstname_from_db";
//...
//Call base method to Save Session and fire Auth/Session callbacks:
return base.OnAuthenticated(authService, session, tokens, authInfo);
//Alternatively avoid built-in behavior and explicitly save session with
//authService.SaveSession(session, SessionExpiry);
//return null;
}
}
Then to get ServiceStack to use your AuthProvider you need to register it with the AuthFeature plugin, e.g:
//Register all Authentication methods you want enabled for this web app
Plugins.Add(new AuthFeature(() => new AuthUserSession(),
new IAuthProvider[] {
new CustomCredentialsAuthProvider(),
}
));
If everything's configured correctly you'll then be able to Authenticate with any of the Service Clients, e.g:
var authResponse = client.Post(new Authenticate
{
provider = "credentials",
UserName = username,
Password = password,
RememberMe = true
});
If successful this will return a populated authResponse, the ss-id/ss-pid Session cookies will also be populated on the client instance which will then let you call AuthOnly Services that are protected with [Authenticate] attribute.
Don't implement Authenticate Service
You never want to implement your own Any(Authenticate request) which ServiceStack already implements. The way to plug into ServiceStack's Authentication is to use a custom provider shown above. You can instead choose to ignore ServiceStack's Authentication in which case you should implement your own Custom Authentication Service but you should not use the existing Authenticate DTO's or [Authenticate] attribute which are apart of ServiceStack's Authentication support and assume that you're calling a registered AuthProvider.
Request DTO's are never nullable
Although unrelated, you also never want to make your Request DTO's nullable, e.g. Any(Authenticate request = null). ServiceStack will always call your Services with a populated Request DTO, or an empty one if no parameters were passed, it will never call your Service without a Request DTO or with a null Request DTO.