At the moment I have a Login-Form with Username (logonParamters.Username) and Password (logonParameters.Password) fields.
The code below is working fine but it presupposes that I have a Password to connect to Exchange. But we want to use Single Sign On and if we do so we don't have a Password right? But EWS wants an Username, Password and Domain. So how do I connect to the Exchange using SSO?
var domain = logonParameters.UserName.Split('\\').First();
using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, domain))
{
// validate the credentials
var username = logonParameters.UserName.Split('\\').Last();
bool isValid = pc.ValidateCredentials(username, logonParameters.Password);
if (!isValid)
{
//throw exception
}
using (UserPrincipal up = UserPrincipal.FindByIdentity(pc, IdentityType.SamAccountName, username))
{
//Connect to ExchangeWebService with those AD Credentials.
}
}
In the EWS Managed API (or WSDL proxy) you can use the current security context (eg logged on user creds) like
ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2016);
service.UseDefaultCredentials = true;
which means getting it to run in a impersonation context and relying on kerberos to do the auth if possible.
Related
I'm trying to create a local Java-based client that interacts with the SurveyMonkey API.
SurveyMonkey requires a long-lived access token using OAuth 2.0, which I'm not very familiar with.
I've been googling this for hours, and I think the answer is no, but I just want to be sure:
Is it possible for me to write a simple Java client that interacts with the SurveyMonkey, without setting up my own redirect server in some cloud?
I feel like having my own online service is mandatory to be able to receive the bearer tokens generated by OAuth 2.0. Is it possible that I can't have SurveyMonkey send bearer tokens directly to my client?
And if I were to set up my own custom Servlet somewhere, and use it as a redirect_uri, then the correct flow would be as follows:
Java-client request bearer token from SurveyMonkey, with
redirect_uri being my own custom servlet URL.
SurveyMonkey sends token to my custom servlet URL.
Java-client polls custom servlet URL until a token is available?
Is this correct?
Yes, it is possible to use OAuth2 without a callback URL.
The RFC6749 introduces several flows. The Implicit and Authorization Code grant types require a redirect URI. However the Resource Owner Password Credentials grant type does not.
Since RFC6749, other specifications have been issued that do not require any redirect URI:
RFC7522: Security Assertion Markup Language (SAML) 2.0 Profile for OAuth 2.0 Client Authentication and Authorization Grants
RFC7523: JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and Authorization Grants
RFC8628: OAuth 2.0 Device Authorization Grant
In any case, if the grant types above do not fit on your needs, nothing prevent you from creating a custom grant type.
Not exactly, the whole point of the OAuth flow is that the user (the client you're accessing the data on behalf of) needs to give you permission to access their data.
See the authentication instructions. You need to send the user to the OAuth authorize page:
https://api.surveymonkey.net/oauth/authorize?api_key<your_key>&client_id=<your_client_id>&response_type=code&redirect_uri=<your_redirect_uri>
This will show a page to the user telling them which parts of their account you are requesting access to (ex. see their surveys, see their responses, etc). Once the user approves that by clicking "Authorize" on that page, SurveyMonkey will automatically go to whatever you set as your redirect URI (make sure the one from the url above matches with what you set in the settings for your app) with the code.
So if your redirect URL was https://example.com/surveymonkey/oauth, SurveyMonkey will redirect the user to that URL with a code:
https://example.com/surveymonkey/oauth?code=<auth_code>
You need to take that code and then exchange it for an access token by doing a POST request to https://api.surveymonkey.net/oauth/token?api_key=<your_api_key> with the following post params:
client_secret=<your_secret>
code=<auth_code_you_just_got>
redirect_uri=<same_redirect_uri_as_before>
grant_type=authorization_code
This will return an access token, you can then use that access token to access data on the user's account. You don't give the access token to the user it's for you to use to access the user's account. No need for polling or anything.
If you're just accessing your own account, you can use the access token provided in the settings page of your app. Otherwise there's no way to get an access token for a user without setting up your own redirect server (unless all the users are in the same group as you, i.e. multiple users under the same account; but I won't get into that). SurveyMonkey needs a place to send you the code once the user authorizes, you can't just request one.
You do need to implement something that will act as the redirect_uri, which does not necessarily need to be hosted somewhere else than your client (as you say, in some cloud).
I am not very familiar with Java and Servelets, but if I assume correctly, it would be something that could handle http://localhost:some_port. In that case, the flow that you describe is correct.
I implemented the same flow successfully in C#. Here is the class that implements that flow. I hope it helps.
class OAuth2Negotiator
{
private HttpListener _listener = null;
private string _accessToken = null;
private string _errorResult = null;
private string _apiKey = null;
private string _clientSecret = null;
private string _redirectUri = null;
public OAuth2Negotiator(string apiKey, string address, string clientSecret)
{
_apiKey = apiKey;
_redirectUri = address.TrimEnd('/');
_clientSecret = clientSecret;
_listener = new HttpListener();
_listener.Prefixes.Add(address + "/");
_listener.AuthenticationSchemes = AuthenticationSchemes.Anonymous;
}
public string GetToken()
{
var url = string.Format(#"https://api.surveymonkey.net/oauth/authorize?redirect_uri={0}&client_id=sm_sunsoftdemo&response_type=code&api_key=svtx8maxmjmqavpavdd5sg5p",
HttpUtility.UrlEncode(#"http://localhost:60403"));
System.Diagnostics.Process.Start(url);
_listener.Start();
AsyncContext.Run(() => ListenLoop(_listener));
_listener.Stop();
if (!string.IsNullOrEmpty(_errorResult))
throw new Exception(_errorResult);
return _accessToken;
}
private async void ListenLoop(HttpListener listener)
{
while (true)
{
var context = await listener.GetContextAsync();
var query = context.Request.QueryString;
if (context.Request.Url.ToString().EndsWith("favicon.ico"))
{
context.Response.StatusCode = (int)HttpStatusCode.NotFound;
context.Response.Close();
}
else if (query != null && query.Count > 0)
{
if (!string.IsNullOrEmpty(query["code"]))
{
_accessToken = await SendCodeAsync(query["code"]);
break;
}
else if (!string.IsNullOrEmpty(query["error"]))
{
_errorResult = string.Format("{0}: {1}", query["error"], query["error_description"]);
break;
}
}
}
}
private async Task<string> SendCodeAsync(string code)
{
var GrantType = "authorization_code";
//client_secret, code, redirect_uri and grant_type. The grant type must be set to “authorization_code”
var client = new HttpClient();
client.BaseAddress = new Uri("https://api.surveymonkey.net");
var request = new HttpRequestMessage(HttpMethod.Post, string.Format("/oauth/token?api_key={0}", _apiKey));
var formData = new List<KeyValuePair<string, string>>();
formData.Add(new KeyValuePair<string, string>("client_secret", _clientSecret));
formData.Add(new KeyValuePair<string, string>("code", code));
formData.Add(new KeyValuePair<string, string>("redirect_uri", _redirectUri));
formData.Add(new KeyValuePair<string, string>("grant_type", GrantType));
formData.Add(new KeyValuePair<string, string>("client_id", "sm_sunsoftdemo"));
request.Content = new FormUrlEncodedContent(formData);
var response = await client.SendAsync(request);
if (!response.IsSuccessStatusCode)
{
_errorResult = string.Format("Status {0}: {1}", response.StatusCode.ToString(), response.ReasonPhrase.ToString());
return null;
}
var data = await response.Content.ReadAsStringAsync();
if (data == null)
return null;
Dictionary<string, string> tokenInfo = JsonConvert.DeserializeObject<Dictionary<string, string>>(data);
return(tokenInfo["access_token"]);
}
}
I followed the quickstart here: https://learn.microsoft.com/en-us/powerapps/developer/common-data-service/webapi/enhanced-quick-start
Which worked great, so then I need to register my app, so I followed this:
https://learn.microsoft.com/en-us/powerapps/developer/common-data-service/walkthrough-register-app-azure-active-directory
But now my unit tests give me the error:
Microsoft.IdentityModel.Clients.ActiveDirectory.AdalServiceException:
AADSTS65001: The user or administrator has not consented to use the
application with ID '[GUID]' named '[AppName]'. Send an interactive
authorization request for this user and resource.
I feel like I understand the error, that the administrator needs to consent. My program is doing some magic in the bakcgorund and the user is not signing in, it is using a set username and password and the user should not be consenting to anyone. Is there any way to set this consent permanently, or force it every time through the Helper class in the first tutorial? All my Google-fu came up empty... Thank you.
You can use something like this:
CrmserviceClient is from Microsoft.Xrm.Tooling.Connector nuget
private CrmServiceClient GenerateService()
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
ServicePointManager.Expect100Continue = true;
ServicePointManager.CheckCertificateRevocationList = true;
ServicePointManager.DefaultConnectionLimit = 10;
var service = new CrmServiceClient(new Uri(organizationUrl), clientId, secret, false, string.Empty);
if (service.IsReady == false)
{
throw new Exception("CrmOrgService isn't ready. " + service.LastCrmError);
}
return service;
}
Or if you want to use connection string you can use this:
Connection string : https://learn.microsoft.com/en-us/dynamics365/customerengagement/on-premises/developer/xrm-tooling/use-connection-strings-xrm-tooling-connect
var connectionString =
ConfigurationManager.ConnectionStrings["XY"].ConnectionString;
var conn = new CrmServiceClient(connectionString);
IOrganizationService orgService = conn.OrganizationServiceProxy;
How to verify IDM does it have an active session for the user signing in?
details - If user'A' has a active session on IDM from browser 'X', When the same user 'A' try to login using browser 'Y', expected behavior identify that user has active session and invalidate the browser'X' session.
Background-
IDM with aspnetIdentity
Client with Implicit grant
(30 sec identitytoken life, does kept renewing access token silently without going to login page, expected to hit some method on the IDM then I can verify user has access or not)!!
Brock has already mentioned about it, It should be at the time of login and logout
It make sense,why its not in Idm. but its definitely possible to provide this as an enhanced feature at least in the coming versions.
Profile Service, IsActive method is the one hit by authorize and
tokenvalidation end point.
so at the time of login persist session, then when the above code hits do the check as per business requirement.
as long as the session is active ( cookie life time) the silent authentication will be passed with the application logic. so this can be controlled by cookie lifetime as well.
public override async Task IsActiveAsync(IsActiveContext context)
{
var sub = context.Subject.GetSubjectId();
var user = await userManager.FindByIdAsync(sub);
//Check existing sessions
if (context.Caller.Equals("AccessTokenValidation", StringComparison.OrdinalIgnoreCase))
{
if (user != null)
context.IsActive = !appuser.VerifyRenewToken(sub, context.Client.ClientId);
else
context.IsActive = false;
}
else
context.IsActive = user != null;
}
signin
public async Task<IActionResult> Login(LoginInputModel model)
{
if (ModelState.IsValid)
{
// To enable password failures to trigger account lockout, set lockoutOnFailure: true
var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberLogin, false);
if (result.Succeeded)
{
//Update security stamp to invalidate existing sessions
//TODO: This didn't invalidate the existing cookie from another client
//var test= _userManager.UpdateSecurityStampAsync(_userManager.FindByEmailAsync(model.Email).Result).Result;
appUser.PersistSession(new UserSession
{
CreatedOn = DateTimeOffset.Now,
DeviceUniqueId = GetDeviceId(),
UserId = _userManager.FindByNameAsync(model.Email).Result.Id,
SId = httpContext.HttpContext.Session.Id,
ClientId= httpContext.HttpContext.Request.QueryString.Value.GetClientIdFromQuery(),
ExpiresOn = DateTimeOffset.Now.AddMinutes(appSettings.SessionTimeOut)
});
_logger.LogInformation(1, "User logged in.");
return RedirectToLocal(model.ReturnUrl);
}
This method has a few drawback when IIS gets restarted and if user has not signed out properly.
there may be a better options this is not the best fit.!
Update:
refer here duplicate/similar question
idmsrv endpoints are missing security change check
Issue raised
Should be like this #tibold
How to implement a two factor authentication using Identity Server 4? The token end point returns a token with a username and password / client credentials.
Can we customize those end points?
Both the methods as per the sample does not allow to customize the end point:
> var tokenClient = new TokenClient(disco.TokenEndpoint, "ro.client", "secret");
> var tokenResponse = await tokenClient.RequestResourceOwnerPasswordAsync("brockallen#gmail.com",
> "Pass123$", "api1");
Is it possible to achieve 2 factor authentication using either asp.net identity Or EF Core implementation?
This shouldn't be a problem at all. When a user is redirected to the Identity Server for login in, if 2FA is enabled then he/she would have to enter the authenticator's code before the Identity Server returns the response back. I have created a repository and blog post series that explain in detail the related concepts. In the AccountController of the IdentityServer you have to check if 2FA is enabled and ask the user to proceed by providing an authenticator code before returning the response.
var signInResult = await _signInManager.PasswordSignInAsync(model.UserName, model.Password, true,
lockoutOnFailure: false);
if (signInResult.RequiresTwoFactor)
{
result.Status = Status.Success;
result.Message = "Enter the code generated by your authenticator app";
result.Data = new {requires2FA = true};
return result;
}
You will also need a TwoFactorAuthenticationController that supports all the 2FA tasks (enable/disable 2FA, sign in with authenticator code/recovery tokens, reset authenticator, etc...)
Is there a way to renew the authentication timeout of a cookie as part of a particular web request? I have an Angular app on top of my MVC 5 project and I need it to keep my server session alive in between requests. I've got the Angular part working, but it appears that hitting a URL on my server is not sufficient to reset the Auth timeout. I am new to Identity so I am probably missing something simple?
My Startup.Auth.cs code:
public void ConfigureAuth(IAppBuilder app)
{
// Configure the db context, user manager and signin manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
// Enable the application to use a cookie to store information for the signed in user
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
ExpireTimeSpan = TimeSpan.FromSeconds(30),
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
// Enables the application to validate the security stamp when the user logs in.
// This is a security feature which is used when you change a password or add an external login to your account.
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(20),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
}
});
}
And my simple method (authorization is set up globally for all requests that do not have [AllowAnonymous]):
[HttpGet]
public HttpResponseMessage KeepAuthAlive()
{
// Renew Auth Cookie - how?
}
Re-SignIn the User (this code assumes you have UserManager and SignInManager available as per the default Account controller and an async ActionResult). I haven't tested this, but it should hopefully work:
ApplicationUser user = await UserManager.FindByIdAsync(User.Identity.GetUserId());
if (user != null)
{
await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
}