MS Graph - Getting App Token (Console Application) - microsoft-graph-api

Console Application - C# .Net 4.6
Dedicated Admin user - I can't have it prompt every time for a login - must run unattended as a native commandline/console application.
I am simply trying to get bearer token to send along with the Graph SDK calls.
I get a token (the same one every time) but am told it's expired. Here is the message:
Access Token Expired, Use Access & Refresh Tokens to Validate
Since this is a console application I do not know how I can get/keep the access and refresh tokens to do this.
FYI: Earlier effort I followed the steps Getting Access Without a User: https://developer.microsoft.com/en-us/graph/docs/concepts/auth_v2_service I could not get past the simple Token HTTP request on that page: unauthorized.
This is my latest effort. Any help would be welcome:
using Microsoft.Graph;
using Microsoft.Identity.Client;
public static async Task<string> GetTokenForAppAsync()
{
if (TokenForApplication == null || TokenForApplicationExpiration <= DateTimeOffset.UtcNow.AddMinutes(5))
{
TokenCacheUser = null;
TokenCacheApplication = null;
ConfidentialClientApplication cl = new ConfidentialClientApplication(Settings.AuthClientId,
returnUrl,
new ClientCredential(Settings.AuthClientSecret),
TokenCacheUser,
TokenCacheApplication);
AuthenticationResult authResult = cl.AcquireTokenForClientAsync(new string[] { "https://graph.microsoft.com/.default" }, true).Result;
TokenForApplication = authResult.AccessToken;
Console.WriteLine(authResult.AccessToken);
}
return TokenForApplication;
}
I am open to any solutions which utilize the MS Graph and Identity Libs.

graph_authentication_example
This is an example of token based authentication for a console application. The application must be run at least one time at which you will be prompted to signin but once that is complete an authentication token is stored on the machine the application runs from.
I run a console application as a task from our server and access the Graph API to get various ActiveDirectory data sets using the Graph Endpoints - Typically I need to login once published and then it runs afterwards - this is in the testing phase just now but seems to work well.
Dependencies:
Must have an Azure Active Directory user which will be used for the login and subsequent authentication. Everything happens in the context of this user.
The following Nuget packages are used:
Microsoft.Graph >= v1.6.2
Microsoft.Graph.Core >= v1.6.2
Microsoft.Identity.Client >= v1.1.0 preview
Microsoft.IdentityModel.Clients.ActiveDirectory >= v3.17.1
Newtonsoft.Json >= v1.0.3
System.Net.Http >= v4.3.3
System.Security.Cryptography.Algorithms >= v4.3.0
System.Security.Cryptography.Encoding >= v4.3.0
System.Security.Cryptography.Primitives >= v4.3.0
You must create an application here [https://apps.dev.microsoft.com/] under same user created above, this will give you your client/app id.
You can see Graph in action here [https://developer.microsoft.com/en-us/graph/graph-explorer/] and login with the user created above to test against your own Azure Active Directory.
Settings
I used a .ini file for storing settings but the values are valid, comments in .ini style - note that there are {name} style text in certain entries, those are for string replacement.
You will see Settings.SomeName - that maps to the following:
[Endpoint]
; we don't want v1.0 because of our needs but it is valid
GraphVersion = beta ; v1.0 or beta
; Common Graph endpoint - we sub version
GraphEndpoint = https://graph.microsoft.com/{version}
[Auth]
; authentication uri
Uri = https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token
; authority uri
Authority = https://login.microsoftonline.com/{tenant}
; if we need to login or re-login
RedirectUri = https%3A%2F%2Flogin.microsoftonline.com%2Fcommon%2Foauth2%2Fnativeclient
; you may have a GUID style tenant but 'common' worked fine since it auth's back to Azure anyway
Tenant = common
; the scopes we needed with Graph, yours may vary
Scopes = { User.ReadBasic.All, User.Read.All, User.ReadWrite.All, Directory.AccessAsUser.All, Directory.Read.All, Directory.ReadWrite.All, Group.ReadWrite.All }
; the id of your azure application - guid
ClientId = xxxx###-2##8-4##9-b##1-ec#########8f2
GrantType = client_credentials
Code Snippets
I tried to include complete functions and indicate separation by indicating which files they come from. These are the main parts for token authentication - mostly code complete with exception to 'private' code.
Starting point: Let's say that in my Program.cs I have a call to the following function, everything flows from here for authentication:
// we are just getting a group by id - CreateAuthenticatedClient() is called before every call to Graph
public static async Task<Group> GetGroupAsync(string groupId)
{
var graphClient = AuthenticationHelper.CreateAuthenticatedClient();
try
{
var group = await graphClient.Groups[groupId].Request().GetAsync();
if (group == null) return null;
return group;
}
catch (ServiceException e)
{
ConsoleHelper.WriteException($"GetGroupAsync.{ServiceErrorString(e)}");
return null;
}
}
AuthenticationHelper.cs - complete class
public class AuthenticationHelper
{
public static string TokenForUser = null;
public static DateTimeOffset TokenForUserExpiration;
// this is the 'magic' where we get the user cache
public static PublicClientApplication IdentityClientApp = new PublicClientApplication(Settings.AuthClientId, Settings.AuthAuthority, TokenCacheHelper.GetUserCache());
private static GraphServiceClient graphClient = null;
// Get an access token for the given context and resourceId. An attempt is first made to
// acquire the token silently. If that fails, then we try to acquire the token by prompting the user.
public static GraphServiceClient CreateAuthenticatedClient()
{
if (graphClient == null)
{
try
{
graphClient = new GraphServiceClient(
Settings.GraphEndpoint,
new DelegateAuthenticationProvider(
async (requestMessage) =>
{
var token = await GetTokenForUserAsync();
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", token);
requestMessage.Headers.Add("azure-graph-test", "manage group membership");
}));
return graphClient;
}
catch (ServiceException sex)
{
Console.WriteLine($"Could not create a graph client service: {sex.Message}");
}
catch (Exception ex)
{
Console.WriteLine($"Could not create a graph client: {ex.Message}");
}
}
return graphClient;
}
/// <summary>
/// get Token for User
/// </summary>
public static async Task<string> GetTokenForUserAsync()
{
if (TokenForUser == null || TokenForUserExpiration <= DateTimeOffset.UtcNow.AddMinutes(5))
{
AuthenticationResult authResult;
try
{
authResult = await IdentityClientApp.AcquireTokenSilentAsync(Settings.AuthScopes, IdentityClientApp.Users.FirstOrDefault());
TokenForUser = authResult.AccessToken;
}
catch (Exception)
{
if (TokenForUser == null || TokenForUserExpiration <= DateTimeOffset.UtcNow.AddMinutes(5))
{
authResult = await IdentityClientApp.AcquireTokenAsync(Settings.AuthScopes);
TokenForUser = authResult.AccessToken;
TokenForUserExpiration = authResult.ExpiresOn;
}
}
}
return TokenForUser;
}
}
TokenCacheHelper.cs - this is a microsoft class that is key to getting/setting the token cache
// Copyright (c) Microsoft Corporation.
// All rights reserved.
// This code is licensed under the MIT License.
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files(the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions :
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
static class TokenCacheHelper
{
/// <summary>
/// Get the user token cache
/// </summary>
public static TokenCache GetUserCache()
{
if (usertokenCache == null)
{
usertokenCache = new TokenCache();
usertokenCache.SetBeforeAccess(BeforeAccessNotification);
usertokenCache.SetAfterAccess(AfterAccessNotification);
}
return usertokenCache;
}
static TokenCache usertokenCache;
/// <summary>
/// Path to the token cache
/// </summary>
public static string CacheFilePath = System.Reflection.Assembly.GetExecutingAssembly().Location + "msalcache.txt";
private static readonly object FileLock = new object();
public static void BeforeAccessNotification(TokenCacheNotificationArgs args)
{
lock (FileLock)
{
args.TokenCache.Deserialize(File.Exists(CacheFilePath)
? File.ReadAllBytes(CacheFilePath)
: null);
}
}
public static void AfterAccessNotification(TokenCacheNotificationArgs args)
{
// if the access operation resulted in a cache update
if (args.TokenCache.HasStateChanged)
{
lock (FileLock)
{
// reflect changes in the persistent store
File.WriteAllBytes(CacheFilePath, args.TokenCache.Serialize());
// once the write operationtakes place restore the HasStateChanged bit to filse
args.TokenCache.HasStateChanged = false;
}
}
}
}

Related

Spring Oauth2 Client, automatically refresh expired access_token

Let me explain my use case.
I need to have a spring boot oauth2 client application (not a resource server As we already have a separate resource server). Also I have following requirements:
For each out going request to resource server, we need to send id_token. (Done by customizing resttemplate).
For any request, no matter if it invokes resource server or not, If access token is expired my application must refresh it automatically (without any user intervention like any popup or redirection.).
If refresh_token is also expired, user must be logged out.
Questions:
For point 2 and 3, I have spent many hours reading documents and code and Stack Overflow but was not able to find the solution (or did not understand). So I decided to put all pieces together which I found on many blogs and documents, and come up with my solution. Below is my solution for point 2.
Can we please have a look to below code and suggest if there could be any problem with this approach?
How to solve point 3 I am thinking of extending solution for point 2 but not sure what code I need to write, can anyone guide me?
/**
*
* #author agam
*
*/
#Component
public class ExpiredTokenFilter extends OncePerRequestFilter {
private static final Logger log = LoggerFactory.getLogger(ExpiredTokenFilter.class);
private Duration accessTokenExpiresSkew = Duration.ofMillis(1000);
private Clock clock = Clock.systemUTC();
#Autowired
private OAuth2AuthorizedClientService oAuth2AuthorizedClientService;
#Autowired
CustomOidcUserService userService;
private DefaultRefreshTokenTokenResponseClient accessTokenResponseClient;
private JwtDecoderFactory<ClientRegistration> jwtDecoderFactory;
private static final String INVALID_ID_TOKEN_ERROR_CODE = "invalid_id_token";
public ExpiredTokenFilter() {
super();
this.accessTokenResponseClient = new DefaultRefreshTokenTokenResponseClient();
this.jwtDecoderFactory = new OidcIdTokenDecoderFactory();
}
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
log.debug("my custom filter called ");
/**
* check if authentication is done.
*/
if (null != SecurityContextHolder.getContext().getAuthentication()) {
OAuth2AuthenticationToken currentUser = (OAuth2AuthenticationToken) SecurityContextHolder.getContext()
.getAuthentication();
OAuth2AuthorizedClient authorizedClient = this.oAuth2AuthorizedClientService
.loadAuthorizedClient(currentUser.getAuthorizedClientRegistrationId(), currentUser.getName());
/**
* Check if token existing token is expired.
*/
if (isExpired(authorizedClient.getAccessToken())) {
/*
* do something to get new access token
*/
log.debug(
"=========================== Token Expired !! going to refresh ================================================");
ClientRegistration clientRegistration = authorizedClient.getClientRegistration();
/*
* Call Auth server token endpoint to refresh token.
*/
OAuth2RefreshTokenGrantRequest refreshTokenGrantRequest = new OAuth2RefreshTokenGrantRequest(
clientRegistration, authorizedClient.getAccessToken(), authorizedClient.getRefreshToken());
OAuth2AccessTokenResponse accessTokenResponse = this.accessTokenResponseClient
.getTokenResponse(refreshTokenGrantRequest);
/*
* Convert id_token to OidcToken.
*/
OidcIdToken idToken = createOidcToken(clientRegistration, accessTokenResponse);
/*
* Since I have already implemented a custom OidcUserService, reuse existing
* code to get new user.
*/
OidcUser oidcUser = this.userService.loadUser(new OidcUserRequest(clientRegistration,
accessTokenResponse.getAccessToken(), idToken, accessTokenResponse.getAdditionalParameters()));
log.debug(
"=========================== Token Refresh Done !! ================================================");
/*
* Print old and new id_token, just in case.
*/
DefaultOidcUser user = (DefaultOidcUser) currentUser.getPrincipal();
log.debug("new id token is " + oidcUser.getIdToken().getTokenValue());
log.debug("old id token was " + user.getIdToken().getTokenValue());
/*
* Create new authentication(OAuth2AuthenticationToken).
*/
OAuth2AuthenticationToken updatedUser = new OAuth2AuthenticationToken(oidcUser,
oidcUser.getAuthorities(), currentUser.getAuthorizedClientRegistrationId());
/*
* Update access_token and refresh_token by saving new authorized client.
*/
OAuth2AuthorizedClient updatedAuthorizedClient = new OAuth2AuthorizedClient(clientRegistration,
currentUser.getName(), accessTokenResponse.getAccessToken(),
accessTokenResponse.getRefreshToken());
this.oAuth2AuthorizedClientService.saveAuthorizedClient(updatedAuthorizedClient, updatedUser);
/*
* Set new authentication in SecurityContextHolder.
*/
SecurityContextHolder.getContext().setAuthentication(updatedUser);
}
}
filterChain.doFilter(request, response);
}
private Boolean isExpired(OAuth2AccessToken oAuth2AccessToken) {
Instant now = this.clock.instant();
Instant expiresAt = oAuth2AccessToken.getExpiresAt();
return now.isAfter(expiresAt.minus(this.accessTokenExpiresSkew));
}
private OidcIdToken createOidcToken(ClientRegistration clientRegistration,
OAuth2AccessTokenResponse accessTokenResponse) {
JwtDecoder jwtDecoder = this.jwtDecoderFactory.createDecoder(clientRegistration);
Jwt jwt;
try {
jwt = jwtDecoder
.decode((String) accessTokenResponse.getAdditionalParameters().get(OidcParameterNames.ID_TOKEN));
} catch (JwtException ex) {
OAuth2Error invalidIdTokenError = new OAuth2Error(INVALID_ID_TOKEN_ERROR_CODE, ex.getMessage(), null);
throw new OAuth2AuthenticationException(invalidIdTokenError, invalidIdTokenError.toString(), ex);
}
OidcIdToken idToken = new OidcIdToken(jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(),
jwt.getClaims());
return idToken;
}
}
I am open for any suggestion to improve my code. Thanks.
There are not enough details to understand your use-case fully. It would be great to understand:
Spring security is rapidly evolving around OAuth2, consider mentioning the version you are using. My answer assumes 5.2+
Are you in servlet (user logged in somehow) or non-servlet (like #Scheduled method) environment
From the limited information and my limited knowledge I have following hints:
Consider using WebClient instead of RestTemplate, this is they way to go for the future. It is reactive but don't be scared. It can be used in "blocking" environment as well, you will not use it's full potential but you can still benefit from its better support for OAuth2
WebClient itself has a ServletOAuth2AuthorizedClientExchangeFilterFunction which does pretty much what you are trying to achieve
When creating ServletOAuth2AuthorizedClientExchangeFilterFunction you pass in AuthorizedClientServiceOAuth2AuthorizedClientManager which is a strategy on how to (re)authenticate client.
Sample configuration may look as follows:
#Bean
public WebClient webClient(ClientRegistrationRepository clientRegistrationRepository, OAuth2AuthorizedClientService authorizedClientService) {
AuthorizedClientServiceOAuth2AuthorizedClientManager manager = new AuthorizedClientServiceOAuth2AuthorizedClientManager(clientRegistrationRepository, authorizedClientService);
manager.setAuthorizedClientProvider(new DelegatingOAuth2AuthorizedClientProvider(
new RefreshTokenOAuth2AuthorizedClientProvider(),
new ClientCredentialsOAuth2AuthorizedClientProvider()));
ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2 = new ServletOAuth2AuthorizedClientExchangeFilterFunction(manager);
oauth2.setDefaultClientRegistrationId("your-client-registratioin-id");
return WebClient.builder()
.filter(oauth2)
.apply(oauth2.oauth2Configuration())
.build();
}
And use it as:
#Autowire
private final WebClient webClient;
...
webClient.get()
.uri("http://localhost:8081/api/message")
.retrieve()
.bodyToMono(String.class)
.map(string -> "Retrieved using password grant: " + string)
.subscribe(log::info);
Hope this helps to move in the right direction! Have fun

External Login without using identity asp.net core 2.0

I'm trying to create an external login scheme for facebook, google and linkedin without using identity framework. I have an api that stores all users and do some authentication stuffs. Right now I'm kind of lost on how to get the information from the external login.
I'm issuing a challenge like this.
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult ExternalLogin(string provider)
{
//Issue a challenge to external login middleware to trigger sign in process
return new ChallengeResult(provider);
}
This works well, it redirects me to either google, facebook or linkedinn authentication.
Now on this part:
public async Task<IActionResult> ExternalLoginCallback()
{
//Extract info from externa; login
return Redirect("/");
}
All I want is to get the information that was provided by the external login.
I have tried what I found from my research,
var result = await HttpContext.AuthenticateAsync(provider);
if (result?.Succeeded != true)
{
return Redirect("/");
}
var externalUser = result.Principal;
var claims = externalUser.Claims.ToList();
First of all I I'm not sure if a simple ?provider=Google on my callback string will pass the provider name I specify so it can be used to check the sign in scheme. I guess this is incorrect. Secondly, I tried hard coding await HttpContext.AuthenticateAsync("Google") and when it reach this code, the debug stops. I'm not sure why.
I've seen the generated code when creating a project with single authentication.
var info = await _signInManager.GetExternalLoginInfoAsync();
Sadly, I'm won't be able to use identity since I don't have a user store and my application will be consuming an API.
First you need to create a custom cookie handler. I myself had problems with:
No IAuthenticationSignInHandler is configured to handle sign in for
the scheme: Bearer
I had to add a cookie handler that will temporarily store the outcome of the external authentication, e.g. the claims that got sent by the external provider. This is necessary, since there are typically a couple of redirects involved until you are done with the external authentication process.
Startup
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(o =>
{
o.TokenValidationParameters = tokenValidationParameters;
})
.AddCookie("YourCustomScheme")
.AddGoogle(googleOptions =>
{
googleOptions.SignInScheme = "YourCustomScheme";
googleOptions.ClientId = "x";//Configuration["Authentication:Google:ClientId"];
googleOptions.ClientSecret = "x";//Configuration["Authentication:Google:ClientSecret"];
//googleOptions.CallbackPath = "/api/authentication/externalauthentication/signin-google";
});
The important part here is "YourCustomScheme".
Now it's time to retrieve the user information from the claims provided by the external authentication in the callback action.
Controller
[AllowAnonymous]
[HttpPost(nameof(ExternalLogin))]
public IActionResult ExternalLogin(ExternalLoginModel model)
{
if (model == null || !ModelState.IsValid)
{
return null;
}
var properties = new AuthenticationProperties { RedirectUri = _authenticationAppSettings.External.RedirectUri };
return Challenge(properties, model.Provider);
}
[AllowAnonymous]
[HttpGet(nameof(ExternalLoginCallback))]
public async Task<IActionResult> ExternalLoginCallback(string returnUrl = null, string remoteError = null)
{
//Here we can retrieve the claims
var result = await HttpContext.AuthenticateAsync("YourCustomScheme");
return null;
}
VoilĂ ! We now have some user information to work with!
Helpful link
http://docs.identityserver.io/en/release/topics/signin_external_providers.html
I too had this issue and see if the below code works for you.
I wanted to extract the full name after Google/FB authentication.
var info = await _signInManager.GetExternalLoginInfoAsync();
TempData["fullname"] = info.Principal.FindFirstValue(ClaimTypes.Name);

How do I convert an external OAuth Identity into a local identity in Umbraco?

I am attempting to develop a proof-of-concept using my company's website as an OAuth authorization server to be consumed by Umbraco via OWIN/Katana. All of the OAuth plumbing appears to be working just fine but Umbraco isn't converting the external identity into a local identity. Instead of being logged into the Umbraco backend, the user lands back on the login page. The only change once the OAuth flow has completed is that Umbraco has created an UMB_EXTLOGIN cookie containing a long encrypted string.
If I login using a local identity directly (i.e. user name and password on the Umbraco backend login page) Umbraco creates 4 cookies: UMB_UCONTEXT, UMB_UPDCHK, XSRF-TOKEN and XSRF-V. I assume I'm missing something that converts the external identity into a local one, but I'm not sure what that is.
Startup.Auth.cs
public partial class Startup
{
public void ConfigureAuth(IAppBuilder app)
{
app.ConfigureBackOfficeMyCompanyAuth(Properties.Settings.Default.ClientId, Properties.Settings.Default.ClientSecret);
}
}
UmbracoMyCompanyAuthExtensions.cs
public static class UmbracoMyCompanyAuthExtensions
{
public static void ConfigureBackOfficeMyCompanyAuth(this IAppBuilder app, string clientId, string clientSecret,
string caption = "My Company", string style = "btn-mycompany", string icon = "fa-rebel")
{
var options = new MyCompanyAuthenticationOptions
{
ClientId = clientId,
ClientSecret = clientSecret,
SignInAsAuthenticationType = Constants.Security.BackOfficeExternalAuthenticationType,
Provider = new MyCompanyAuthenticationProvider(),
CallbackPath = new PathString("/MyCompanySignIn")
};
options.ForUmbracoBackOffice(style, icon);
options.Caption = caption;
app.UseMyCompanyAuthentication(options);
}
}
MyCompanyAuthenticationExtension.cs
public static class MyCompanyAuthenticationExtensions
{
public static IAppBuilder UseMyCompanyAuthentication(this IAppBuilder app, MyCompanyAuthenticationOptions options)
{
if (app == null)
{
throw new ArgumentNullException("app");
}
if (options == null)
{
throw new ArgumentNullException("options");
}
app.Use(typeof(MyCompanyAuthenticationMiddleware), new object[] { app, options });
return app;
}
public static IAppBuilder UseMyCompanyAuthentication(this IAppBuilder app, string clientId, string clientSecret)
{
MyCompanyAuthenticationOptions options = new MyCompanyAuthenticationOptions
{
ClientId = clientId,
ClientSecret = clientSecret
};
return app.UseMyCompanyAuthentication(options);
}
}
My custom implementation of AuthenticationHandler<T>.AuthenticateCoreAsync() returns an AuthenticationTicket with the following claims and properties.
Claims
GivenName = My First Name
FamilyName = My Last Name
Name = My Full Name
Email = My Email Address
Properties
.redirect = /umbraco/
Dont have any code ready at hand but from past experiences, using Facebook OAuth, you will have to wire in your own logic to basically either or both, convert you OAuth object (user) into an umbraco one.
When we done it previously the first time a user does it (checking by email), it creates a new user then every subsequent login get the umbraco user by their email and log them in in code. This was the same for both backend users and front end members.
So after much wheel spinning, I finally figured it out. The resulting ClaimsIdentity didn't contain a NameIdentifier claim. I had my OAuth middleware include that claim using the email address as the value and it started working.
FYI, if you're looking to autolink external and local accounts upon external login, here's a really good example that worked for me.

How to acquire a new Access token using Refresh token in Google OAuth 2.0 in .NET?

I have built MVC app using OAuth 2.0 to Access Google APIs.
On thirst call I receive an Access token + Refresh token.
Next calls come without a Refresh token, its ok, I saved it on a first call.
After 1 hour Access token expires and I need to get a new one, using previously saved refresh token.
How do I check that Access token expired? Didnt see any IsExpired properties.
What is the proper syntax to acquire a new Access token using Refresh token (for MVC app)? Couldnt find any reference or documentation how to do that.
Should I write any new code or call existing API to do that?
Where should I do that, in my HomeController's Index action or before calling any Google API?
My app is built as described in here (basically the same code), but no code to acquire a new Access token: https://developers.google.com/api-client-library/dotnet/guide/aaa_oauth#web-applications-aspnet-mvc
Thank you
For more details I added here how I wrote the code.
HomeController:
public async Task<ActionResult> Index(CancellationToken cancellationToken)
{
if (result == null || result.Credential == null)
{
result = await new AuthorizationCodeMvcApp(this, new AppFlowMetadata()).AuthorizeAsync(cancellationToken);
if (result.Credential == null) return new RedirectResult(result.RedirectUri);
if (!string.IsNullOrEmpty(result.Credential.Token.RefreshToken))
{
SaveRefreshTocken(result.Credential.Token.RefreshToken);
}
}
return View();
}
SaveRefreshTocken - just saves a Refresh token in web.config.
public ActionResult Gmail()
{
if (result == null || result.Credential == null) throw new Exception("expired_credential");
return PartialView(GmailManager.GetGmail(result.Credential));
}
And, simplified GmailManager class:
public static class GmailManager
{
public static List<Message> GetGmail(UserCredential credential)
{
var mygmail = new MyGmail();
var service = new GmailService(new BaseClientService.Initializer { HttpClientInitializer = credential });
var request = service.Users.Messages.List("me");
request.Q = "in:inbox is:unread";
var messages = request.Execute().Messages;
return messages;
}
}
Question - where and how should I USE refresh token?
If I saved it, I would have to use it when Access token expires to get a new Access token, right?
However it doesnt seem like its trying to acquire a new access token automatically:

How can I run Internet Explorer Selenium tests as a specific domain user?

I have a ASP.NET MVC website that uses Windows Authentication to control access. I would like to have a specflow selenium test that checks the configuration is correct by attempting to visit the site as a non-authorised user.
As we're using domain accounts to control access there isn't a username/password login screen. The credentials of the current user are automatically passed to the site by the browser.
So for my Selenium test I need to be able to run Internet Explorer as a specific user.
I have found a number of articles about windows impersonation and I can switch to my test user during the running of the test (using the code from http://support.microsoft.com/kb/306158). However if I then create an InternetExplorerDriver it starts internet explorer with my credentials rather than the test user's (although this question and answer suggests that it should work https://sqa.stackexchange.com/questions/2277/using-selenium-webdriver-with-windows-authentication).
I can also explicitly start an Internet Explorer process as my test user, but I can't see a way of binding an InternetExplorerDriver to an already running Internet Explorer process, so this may be a dead end.
My code, basically taken from the MSDN page above is below. In the debugger I can see that WindowsIdentity.GetCurrent().Name is "testUser" in all the steps of the test.
namespace MyProject.Specs
{
using NUnit.Framework;
using OpenQA.Selenium;
using OpenQA.Selenium.IE;
using System;
using System.Runtime.InteropServices;
using System.Security.Principal;
using TechTalk.SpecFlow;
[Binding]
public class AuthorisationSteps
{
public const int LOGON32_LOGON_INTERACTIVE = 2;
public const int LOGON32_PROVIDER_DEFAULT = 0;
private static WindowsImpersonationContext impersonationContext;
private static IWebDriver driver;
[BeforeScenario]
public static void impersonateUser()
{
if (!impersonateValidUser("testUser", "testDomain", "password"))
{
throw new Exception();
}
driver = new InternetExplorerDriver();
}
[AfterScenario]
public static void cleanupUser()
{
undoImpersonation();
driver.Quit();
}
[Given(#"I am an unauthorised user")]
public void GivenIAmAnUnauthorisedUser()
{
var temp = WindowsIdentity.GetCurrent().Name;
}
[When(#"I go to the home page")]
public void WhenIGoToTheHomePage()
{
var temp = WindowsIdentity.GetCurrent().Name;
driver.Navigate().GoToUrl(BaseUrl);
}
[Then(#"I should see an error page")]
public void ThenIShouldSeeAnErrorPage()
{
var temp = WindowsIdentity.GetCurrent().Name;
Assert.That(driver.Title.Contains("Error"));
}
[DllImport("advapi32.dll")]
public static extern int LogonUserA(String lpszUserName,
String lpszDomain,
String lpszPassword,
int dwLogonType,
int dwLogonProvider,
ref IntPtr phToken);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int DuplicateToken(IntPtr hToken,
int impersonationLevel,
ref IntPtr hNewToken);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool RevertToSelf();
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public static extern bool CloseHandle(IntPtr handle);
private static bool impersonateValidUser(String userName, String domain, String password)
{
WindowsIdentity tempWindowsIdentity;
var token = IntPtr.Zero;
var tokenDuplicate = IntPtr.Zero;
if (RevertToSelf())
{
if (LogonUserA(userName, domain, password, LOGON32_LOGON_INTERACTIVE,
LOGON32_PROVIDER_DEFAULT, ref token) != 0)
{
if (DuplicateToken(token, 2, ref tokenDuplicate) != 0)
{
tempWindowsIdentity = new WindowsIdentity(tokenDuplicate);
impersonationContext = tempWindowsIdentity.Impersonate();
if (impersonationContext != null)
{
CloseHandle(token);
CloseHandle(tokenDuplicate);
return true;
}
}
}
}
if (token != IntPtr.Zero)
{
CloseHandle(token);
}
if (tokenDuplicate != IntPtr.Zero)
{
CloseHandle(tokenDuplicate);
}
return false;
}
private static void undoImpersonation()
{
impersonationContext.Undo();
}
}
}
We have many enterprise clients that use Windows Authentication for intranet facing applications and we are starting to run many Selenium tests for confirmation, regression, etc.
We've taken the helpful code from Steven's answer and refactored it into a re-usable class similar to other Impersonate posts that just weren't working for us because we wanted the tests to work both locally in development and deployed as part of the Visual Studio Team System release process.
The uri method was not working locally and neither were impersonating methods using Win32 native methods.
This one worked so here it is.
Example of a test using Steven's code refactored into a helper
[TestMethod]
public void ThisApp_WhenAccessedByUnathorizedUser_ShouldDisallowAccess()
{
string userName = "ThisAppNoAccess";
string password = "123456";
string domainName = Environment.MachineName;
using (new Perkins.Impersonator(userName, domainName, password))
{
// - Use Remote Web Driver to hook up the browser driver instance launched manually.
using (var driver = new RemoteWebDriver(new Uri("http://localhost:9515"), DesiredCapabilities.Chrome()))
{
var desiredUri = Helper.Combine(Helper.BaseURL, "/ThisApp/#/appGrid");
TestContext.WriteLine("desiredUri: {0}", desiredUri);
driver.Navigate().GoToUrl(desiredUri);
Helper.WaitForAngular(driver);
var noPermissionNotificationElement = driver.FindElementByXPath("//div[#ng-show='!vm.authorized']/div/div/div/p");
var showsNoPermissionNotification = noPermissionNotificationElement.Text.Contains("You do not have permissions to view ThisApp.");
Assert.AreEqual(true, showsNoPermissionNotification, "The text `You do not have permissions to view ThisApp.` is not being displayed!");
}
}
}
The helper class
// Idea from http://stackoverflow.com/a/34406336/16008
// - Launch the browser driver manually with other user's credentials in background
public class Perkins
{
public class Impersonator : IDisposable
{
Process _driverProcess = null;
string _driverPath = #"chromedriver.exe";
/// <summary>
/// Impersonates the specified user account by launching the selenium server under that account. Connect to it via RemoteWebDriver and localhost on port 9515.
/// </summary>
/// <remarks>
/// We may later want to enhance this by allowing for different ports, etc.
/// </remarks>
/// <param name="userName">Name of the user</param>
/// <param name="domainName">Name of the domain or computer if using a local account.</param>
/// <param name="password">The password</param>
public Impersonator(string userName, string domainName, string password)
{
ProcessStartInfo processStartInfo = new ProcessStartInfo(_driverPath);
processStartInfo.UserName = userName;
System.Security.SecureString securePassword = new System.Security.SecureString();
foreach (char c in password)
{
securePassword.AppendChar(c);
}
processStartInfo.Password = securePassword;
processStartInfo.Domain = domainName; // this is important, mcollins was getting a 'stub received bad data' without it, even though rglos was not
processStartInfo.UseShellExecute = false;
processStartInfo.LoadUserProfile = true; // this seemed to be key, without this, I get Internal Server Error 500
Thread startThread = new Thread(() =>
{
_driverProcess = Process.Start(processStartInfo);
_driverProcess.WaitForExit();
})
{ IsBackground = true };
startThread.Start();
}
public void Dispose()
{
// - Remember to close/exit/terminate the driver process and browser instance when you are done.
if (_driverProcess != null)
{
// Free managed resources
if (!_driverProcess.HasExited)
{
_driverProcess.CloseMainWindow();
_driverProcess.WaitForExit(5000);
// Kill the process if the process still alive after the wait
if (!_driverProcess.HasExited)
{
_driverProcess.Kill();
}
_driverProcess.Close();
}
_driverProcess.Dispose();
_driverProcess = null;
}
}
}
}
Perhaps this will help someone else with the same issue.
This is in fact possible. I ran into the exact problem you had. Basically, here are the steps you need to do.
Launch the browser driver manually with other user's credentials in background
Process driverProcess;
string driverPath; // The path to Selenium's IE driver.
ProcessStartInfo info = new ProcessStartInfo(driverPath)
{
UserName = "UserName", // The user name.
Password = new SecureString(), // The password for the user.
UseShellExecute = false,
LoadUserProfile = true,
Arguments = "about:blank"
};
// Start the driver in background thread
Thread startThread = new Thread(
() => {
try
{
driverProcess = Process.Start(info);
driverProcess.WaitForExit();
}
catch
{
// Close the process.
}
})
{
IsBackground = true
};
startThread.Start();
Use Remote Web Driver to hook up the browser driver instance launched manually.
var remoteDriver = new RemoteWebDriver(Uri("http://localhost:5555"), DesiredCapabilities.InternetExplorer());
Remember to close/exit/terminate the driver process and browser instance when you are done.
// Close the process when done.
if (driverProcess != null)
{
// Free managed resources
if (!driverProcess.HasExited)
{
driverProcess.CloseMainWindow();
driverProcess.WaitForExit(5000);
// Kill the process if the process still alive after the wait
if (!driverProcess.HasExited)
{
driverProcess.Kill();
}
driverProcess.Close();
}
driverProcess.Dispose();
driverProcess = null;
}
This similar question links to this Microsoft support article. Essentially you need
System.Security.Principal.WindowsImpersonationContext impersonationContext;
impersonationContext =
((System.Security.Principal.WindowsIdentity)User.Identity).Impersonate();
IWebDriver webDriver = new InternetExplorerDriver();
// do your stuff here.
impersonationContext.Undo();
There's additional code in the support article about impersonating a specific user.
Do you have a couple of old PCs? Or the capacity for some virtual machines?
If so, build a Selenium Grid set-up, and configure one to automatically login as the desired domain user and one as a non-domain user.
http://code.google.com/p/selenium/wiki/Grid2
I was having same problem when I was doing automation project for web based application which required window authentication. However, I have achieved this with using firefox, following are the steps to achieve it.
FIREFOX SETUP
OPEN RUN DIALOG OF YOUR SYSTEM AND TYPE 'firefox.exe -p' (CLOSE YOUR FIREFOX BROWSER BEFORE RUNNING THIS COMMAND) http://www.wikihow.com/Create-a-Firefox-Profile
CLICK ON CREATE PROFILE AND GIVE A NAME AS REQURIED
SELECT CREATED PROFILE AND START BROWSER AND OPEN ADD-ONS MANAGER (TOOLS - ADD-ONS)
SEARCH FOR 'AutoAuth' AND INSTALL IT. IT WILL ASK FOR RESTART, DO IT
ONCE THE FIREFOX IS RESTARTED, THAN OPEN URL IT WILL ASK YOU FOR AUTHENTICATION
ENTER USERNAME AND PASSWORD - SUBMIT IT, FIREFOX WILL ASK YOU TO REMEMBER THE PASSWORD
CLICK ON REMEMBER AND IT WILL SAVE THE PASSWORD IN FIREFOX PROFILE
COPY CREATED FIREFOX PROFILE AND SAVE IT TO REQUIRED FOLDER
IN YOUR SELENIUM SCRIPT CALL ABOVE CREATED PROFILE WITH FIREFOX DRIVER AND PASS THE SAME URL, IT WILL NOT ASK FOR AUTHENTICATION DIALOG
This is working very successfully in my project.
We use https://stackoverflow.com/a/31540010/3489693 approach for IE and Chrome over 2 years. It works fine
So it seems the problem that the question is trying to circumvent has to do with NTLM Auto Login. See Google Chrome and NTLM Auto Login Using Windows Authentication
The solutions above did not work for me since the auto-login would successfully authenticate with any user on my system, so it didn't matter which user I used for impersonation.
However, I noticed that you can outsmart auto-login by replacing localhost with any other domain name, such as the local IP address. No impersonation required :)
This may / may not work.
Try to launch your site in "CHROME".
Hit F-12, go to Application Tab -> Cookies -> Click on your site link. on left hand side look for something that represent your session id, may be JSESSIONID or similar that represents user's session, copy that.
Now open your Internet Explorer,
hit F-12 and manually create that JSESSIONID ( or similar key ) by running this command in console window
document.cookie = "JSESSIONID=your-session-id-from-chrome"
hit play button to execute script
Refresh your browser

Resources