I have two sites https://www.somesite.com (user site) and https://admin.anothersite.com (admin site) and I am using Identity Server 3 for access control, this is hosted on https://identity.somesite.com.
The sites are configured in identity server as the same client (different redirect urls) with cookie based authentication. I’d like to provide a mechanism where users of the admin site can impersonate users of the user site.
I’ve seen that I can issue cookies using the IssueLoginCookie, but that call needs to be on the identity server, so given that it’s on another domain, I can’t see how that would work.
How can I go about supporting user impersonation in identity server?
Update
I now have the admin site generate a url like so:
var url = 'http://localhost:61826/connect/authorize'
+ '?state=' + encodeURIComponent(((Date.now() + Math.random()) * Math.random()).toString().replace(".", ""))
+ '&nonce=' + encodeURIComponent(((Date.now() + Math.random()) * Math.random()).toString().replace(".", ""))
+ '&client_id=mvc'
+ '&redirect_uri=' + encodeURIComponent('http://localhost:64822/')
+ '&scope=' + encodeURIComponent('openid profile roles api1')
+ '&acr_values=' + encodeURIComponent('loginas:3230')
+ '&response_type=' + encodeURIComponent('id_token token')
+ '&prompt=login';
window.location.href = url;
This allows me to pickup the login event in the PreAuthenticateAsync method on my custom IUserService and intercept the login. My current implementation is:
public override async Task PreAuthenticateAsync(PreAuthenticationContext context)
{
if (context.SignInMessage.AcrValues.Any(acr => acr.StartsWith("loginas:")))
{
// Would need to also ensure that the user has the relevant persmissions to impersonate another user
var subjectId = _owinContext.Authentication.User.GetSubjectId();
var login = new AuthenticatedLogin
{
Name = "Impersonating For Fun",
Subject = "3230",
Claims = new List<Claim>
{
new Claim(Constants.ClaimTypes.Subject, "3230")
},
PersistentLogin = true,
IdentityProvider = Constants.BuiltInIdentityProvider,
AuthenticationMethod = "Cookies"
};
_owinContext.Environment.IssueLoginCookie(login);
var impersonationClaims = new List<Claim>
{
new Claim("AdminUserId", subjectId)
};
context.AuthenticateResult = new AuthenticateResult("3230", "Impersonating For Fun", impersonationClaims);
}
await Task.FromResult(0);
}
The user is not shown the login page and is correctly redirected to the target url. However, the user has not changed to the new user, but rather remains as the original user. What am I missing?
Are you setting the domain wide cookie? Can you confirm it in browser?
Related
We are implementing MFA using Microsoft Azure. We run wso2 identity server 5.10.4.
What I would like to see happen, is when a user logs in, if they have certain memberOf AD roles, Federation happens. If not, it just uses basic auth/normal log in.
I've been approaching this problem using Adaptive Authentication custom scripting. On a service provider, I created two login steps. Step 1 is basicauth. Step 2 is our MS federated authenticator.
var arrayOfRoles = ["employee","student"];
var onLoginRequest = function(context) {
executeStep(1, {
onSuccess: function (context) {
// Extracting authenticated subject from the first step
var memberOfClaim = 'http://wso2.org/claims/employeeType';
var user = context.currentKnownSubject;
var roles = context.currentKnownSubject.localClaims[memberOfClaim];
foundRole = '-1';
var arrayOfRolesLen = arrayOfRoles.length;
for (var i = 0; i < arrayOfRolesLen; i++) {
searchRole = roles.indexOf(arrayOfRoles[i]);
if (searchRole >= 0) {
foundRole = searchRole;
}
}
if (foundRole >= 0 ) {
Log.info(user.username + ' found a role, indexof=' + foundRole);
// Step 2 is MFA
executeStep(2);
}
}
});
};
The script correctly finds the person's AD memberOf values, and I'm able to execute step 2 using this script. The problem is that a person logs in once to wso2, then if the roles are matched and exectuteStep(2) is invoked, they are prompted with another log in screen for wso2.
How can I prevent a second log in when a person matches the conditions for step 2? Or is this the wrong approach to making a role based decision about when to authenticate using basicauth and when to authenticate using federation?
edit:
Responding to some comments below about using identity-first.
If I setup three steps 1) id first, 2) basic 3) federated, I am prompted for the username again. Step one, a username prompt from wso2. Step two, a username and password prompt. Ditto with step 3.
One difference I see in the below screenshots, is that there is a 'username and password' authenticator. I don't have that available to me in the drop down boxes. Just jwt-basic, and basic. My script using three auth steps looks like this:
var arrayOfRoles = ["PCC_EMPLOYEE_ACTIVE","PCC_STUDENT_CREDIT"];
var onLoginRequest = function(context) {
executeStep(1, {
onSuccess: function (context) {
// Extracting authenticated subject from the first step
var memberOfClaim = 'http://wso2.org/claims/employeeType';
var user = context.currentKnownSubject;
var roles = context.currentKnownSubject.localClaims[memberOfClaim];
foundRole = '-1';
var arrayOfRolesLen = arrayOfRoles.length;
for (var i = 0; i < arrayOfRolesLen; i++) {
searchRole = roles.indexOf(arrayOfRoles[i]);
if (searchRole >= 0) {
foundRole = searchRole;
}
}
Log.info('found role is equal to: ' + foundRole);
if (foundRole >= 0 ) {
Log.info(user.username + ' found a role, indexof=' + foundRole);
// Step 3 is Azure idp.
executeStep(3);
} else {
// Step 2 is basic auth.
executeStep(2);
}
}
});
};```
You may try "identifier First" login with hasRole() to implement your flow.
i.e.
Step1 = Identifier
Step2 = Basic
Step3 = Azure
Let me explain what you have written here using the script.
So you have two auth steps.
Basic auth (username and password)
Federated IDP (Azure)
If you have not enabled adaptive script, after the basic auth step (step1), the user needs to authenticate against the FIDP (step2).
According to the current script that you have written, all the users will have a username and password authentication step (step1). Upon the success of that step, FIDP authentication will be enabled (step2) if the user has a certain set of roles.
Now consider a user who has those specified roles. When that user logins, first the user will see the username password login page. After that, the user will see the azure login page if the user has no active session in Azure (If SSO is enabled, the user might not see the second screen). So it is expected to see two login screens.
So what you have done here is correct, but the user experience is kind of weird. A similar scenario is explained here.
Edited: The better approach is to use the identifier first login method. You can read it here. Please find the approach that I have tried.
I have added 3 steps as follows.
Next I have added a role based adaptive script.
With this approach if I have the role admin, I will only see a input box to provide my password.
But for google it will show google sign-in. If you already have a session then only the consent will be seen. We cannot skip this since this is not in our control.
I configured Identity Server:
public void Configuration(IAppBuilder app)
{
var factory = new IdentityServerServiceFactory().UseInMemoryClients(new Client[] {
new Client()
{
ClientName = "MyClient",
ClientId = "MyClientId",
Enabled = true,
Flow = Flows.Implicit,
RedirectUris = new List<string> { "MyClientServer/callback" },
};
});
}
and client server:
public void Configuration(IAppBuilder app)
{
var cookieOptions = new CookieAuthenticationOptions();
cookieOptions.AuthenticationType = "Cookies";
app.UseCookieAuthentication(cookieOptions);
var authenticationOptions = new OpenIdConnectAuthenticationOptions() {
Authority = "https://MyIdentityServer/core",
ClientId = "MyClientId",
SignInAsAuthenticationType = "Cookies",
UseTokenLifetime = true,
RedirectUri = "MyClientServer/callback"
});
app.UseOpenIdConnectAuthentication(authenticationOptions);
}
When user login with "Remember Me" option Identity cookie has expired date:
idsvr.session expires 04 October ...
But client cookie does not:
.AspNet.Cookies at end of session
What should I do to set the same expiration date to client cookie?
UPDATE:
I can set any expiration date in client application:
authenticationOptions.Provider = new CookieAuthenticationProvider()
{
OnResponseSignIn = (context) =>
{
var isPersistent = context.Properties.IsPersistent;
if (isPersistent) // Always false
{
context.CookieOptions.Expires = DateTime.UtcNow.AddDays(30);
}
}
};
But I cannot determine when to set expiration date. It should be set only when user selects "Remember Me", but IsPersistent option always false on client side.
The problem exists on simple boilerplate project too:
https://identityserver.github.io/Documentation/docsv2/overview/mvcGettingStarted.html
UPDATE2:
I need client cookie to be persistent because of bug in Safari - https://openradar.appspot.com/14408523
Maybe some workaround exists, so I can pass expiration date in callback from Identity to Client?
UPDATE3:
Actually, our Identity and Client servers have same parent domain like app.server.local and id.server.local. Maybe I can pass expiration date via additional cookie that belongs to parent domain (.server.local)? But I have no idea where it can be written on Identity, and where it can be applied on Client.
A cookie issued by IdentityServer and a cookie issued by a client application are not linked in any way. IdentityServer does not have any control over cookies in a client application.
When you log in to IdentityServer, you are issued a cookie that tracks the authenticated user within IdentityServer. This saves the user from entering their credentials for every client application, facilitating single sign on.
By default this cookie lasts for that session (so it expires once the browser closes), otherwise if you set "remember me" it will last for a set number of days, across sessions.
A cookie in a client application would be issued upon successful verification of an identity token from IdentityServer. This cookie can have any expiration time, any policy, any name. It's completely controlled by the client application. In your case client cookie expiration can be set in the CookieAuthenticationOptions in your client application.
You need to handle the cookie auth events. The open id middleware just creates an auth cookie, so you can handle all aspects of this cookie from those events. You'll need to look at the events and with a little trial and error you should be able to manage the cookie lifetime.
You can do it at the java-script by using following code in here I have created this cookie to expires within 14 days.
var exdate = new Date();
exdate.setDate(exdate.getDate() + 14);
document.cookie = "yourcookie=" + yourCookieValue + ";expires=" + exdate.toUTCString() + ";";
We use to create email delegates through Google Email Settings API, but after the deprecation of OAuth 1.0 we were no longer able to authenticate properly. After doing some research I think we should create a service account, delegate domain-wide access for that service account, then authenticate using it. However I can't seem to get it to work, all I receive from Google is 401 unauthorized. Does someone know what I am doing wrong? Here is most of the code, I'm using .Net/c# and I'm using Google Apps for business.
ServiceAccountCredential credential = new ServiceAccountCredential(new ServiceAccountCredential.Initializer("serviceAccountEmail")
{
Scopes = new[] { "https://apps-apis.google.com/a/feeds/emailsettings/2.0/ " },
User = "admin email string"
}.FromCertificate({X509 certificate from service account p12 file}));
credential.RequestAccessTokenAsync(System.Threading.CancellationToken.None).Wait(-1);
GoogleMailSettingsService service = new GoogleMailSettingsService("domain name", "appname");
service.SetAuthenticationToken(credential.Token.AccessToken);
service.CreateDelegate("delegator", "delegate");
For those who may need this answer in the future, I was able to provide a solution through the following. For reference I am running a web app using MVC framework, but the solution could be tweaked for a console or GUI standalone app as well.
Basically, I was able to authenticate the GoogleMailSettingsService.Service.RequestFactory with a GOAuth2RequestFactory object.
For instance:
GoogleMailSettingsService service = new GoogleMailSettingsService("domain", "applicationName");
service.RequestFactory = new GOAuth2RequestFactory("service", "AppName", new OAuth2Parameters() { AccessToken = AuthorizationCodeWebApp.AuthResult.Credential.Token.AccessToken });
Now for the AuthorizationCodeWebApp.AuthResult I implemented the following:
public async Task<ActionResult> DelegationMenu(CancellationToken cancellationToken)
{
var result = await new AuthorizationCodeMvcApp(this, new AppFlowMetadata()).AuthorizeAsync(cancellationToken);
if (result.Credential == null)
return new RedirectResult(result.RedirectUri); //Will redirect to login page for Google Admin to authenticate.
Session["AuthResult"] = result;
return View();
}
public class AppFlowMetadata : FlowMetadata
{
private static readonly IAuthorizationCodeFlow flow =
new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = new ClientSecrets
{
ClientId = "ClientId",
ClientSecret = "ClientSecret"
},
Scopes = new[] { "https://apps-apis.google.com/a/feeds/emailsettings/2.0/" },
DataStore = new FileDataStore("C:\\OAuth2.0Tokens")
});
public override string GetUserId(Controller controller)
{
var user = controller.Session["user"];
if (user == null)
{
user = Guid.NewGuid();
controller.Session["user"] = user;
}
return user.ToString();
}
public override IAuthorizationCodeFlow Flow
{
get { return flow; }
}
}
A service account isn't required for this action. The Email Settings API, within the Admin SDK, allows a Super Admin to set a delegation for an account within the domain without the need to impersonate the user via a service account.
Check out this section of the Developers site for more information on this API. You can also test this on the OAuth Playground and add delegates right from there.
I am using below piece of code to list all domain users in my simple Console application
var certificate = new X509Certificate2("D:\\3acf2c2008cecd33b43de27e30016a72e1482c41-privatekey.p12", "notasecret", X509KeyStorageFlags.Exportable);
var privateKey = certificate.Export(X509ContentType.Cert);
var provider = new AssertionFlowClient(GoogleAuthenticationServer.Description, certificate)
{
ServiceAccountId = "877926787679-b7fd15en1sh2oc65e164v90cfcvrfftq#developer.gserviceaccount.com",
Scope = DirectoryService.Scopes.AdminDirectoryUserReadonly.GetStringValue(),
ServiceAccountUser = "user1#05.mygbiz.com"
};
var auth = new OAuth2Authenticator<AssertionFlowClient>(provider, AssertionFlowClient.GetState);
DirectoryService dirService = new DirectoryService(new BaseClientService.Initializer()
{
Authenticator = auth,
ApplicationName = "My APP"
});
Users users = dirService.Users.List().Execute();
Execute() method errors out saying Bad Request.
Questions:
How to overcome this issue?
Does this Admin SDK support trial version of Google APP account?
I have updated service account Client ID in Google Console and also updated in Admin Console with below scopes
https://www.googleapis.com/auth/admin.directory.group
https://www.googleapis.com/auth/admin.directory.user
and also set API access check box. Do I missing something in settings?
Like JoBe said, you should include the domain parameter.
happy_user = service.users().list(domain='mydomain.com').execute()
This has worked for me.
I'm looking for a way to authenticate a user (given a username and password) via the Microsoft CRM 4.0 Web Services API. Ideally, I'd like to filter down a list of projects based on which ones the logged in user has access to. i may be able to figure out the second part but I can't find a way to authenticate the user. The way all of the cals are currently made in the web service is via:
MyWebServices.CrmService svc = new MyWebServices.CrmService();
MyWebServices.CrmAuthenticationToken token = new MyWebServices.CrmAuthenticationToken();
token.OrganizationName = "MyCRM";
token.AuthenticationType = 0;
svc.CrmAuthenticationTokenValue = token;
svc.PreAuthenticate = true;
svc.Credentials = System.Net.CredentialCache.DefaultCredentials;
svc.Credentials = new NetworkCredential("hj", "mypass", "mydomain");
Then calls can be made via the service. I guess I could potentially try to authenticate to CRM via the user's username/password but it feels wrong somehow.
If you are in an on-premise environment, you should be able to use the following code to get a valid CRM service that can be used to retrieve your projects.
public static Microsoft.Crm.SdkTypeProxy.CrmService GetCrmService(string crmServerUrl, string organizationName, System.Net.NetworkCredential networkCredential)
{
// Setup the Authentication Token
CrmAuthenticationToken crmAuthenticationToken = new CrmAuthenticationToken
{
OrganizationName = organizationName,
AuthenticationType = 0
};
var crmServiceUriBuilder = new UriBuilder(crmServerUrl) { Path = "//MSCRMServices//2007//CrmService.asmx" };
// Instantiate a CrmService
var crmService = new Microsoft.Crm.SdkTypeProxy.CrmService
{
Url = crmServiceUriBuilder.ToString(),
UseDefaultCredentials = false,
Credentials = networkCredential,
CrmAuthenticationTokenValue = crmAuthenticationToken
};
return crmService;
}