I have a WCF service that needs to ultimately get a token to talk to a web api service hosted in azure. Our on premise active directory is synced with our azure account.
Initially I had a play in a win forms app and got a token successfully using the following:
AuthenticationResult authResult = authContext.AcquireToken(apiResourceId, clientId, redirectUri);
This though popped up a login dialog so not much use for a windows service. I then investigated the use of AcquireTokenSilent(). This however kept throwing an exception telling me to call AcquireToken so back to square 1.
My next port of call was to look at AcquireTokenByAuthorizationCode(). My problem with this though is how to acquire an authorisation code which is the first parameter.
I've tried:
var url = authContext.GetAuthorizationRequestURL(apiResourceId, clientId, redirectUri, UserIdentifier.AnyUser, string.Empty);
HttpClient hc = new HttpClient();
HttpResponseMessage hrm = hc.GetAsync(url).Result;
This though just returns an html page showing i think a microsoft login page. It certainly doesn't contain any code.
Any ideas on what I am doing wrong?
The AcquireTokenSilent works only if you already have tokens in the cache, which is not the case in your scenario. The AcquireTokenByAuthorizationCode is meant to be sued on the server side.
The main ways in which you can get a token without popping out a dialog on a client are shown in https://github.com/Azure-Samples/active-directory-dotnet-native-headless, but there are important limitations. If they work in your scenario fine, otherwise you might consider creating a persistent cache, priming it by doing one interactive authentication, and then keep using the same cache from your service via AcquireTokenSilent. The cached refresh token will last 90 days as long as you use it at least once every 14 days.
Related
Just looking into Blazor and my company decided to explore Server Side App (no WASM).
I got the basic project setup and because we want the app to work with out current user db, I modified our User Server (Microservice) to authenticate a user and return a JWT token. In the Login page once we have the token, we authenticate and the cookie is stored.
var auth = await HttpContext.AuthenticateAsync();
auth.Properties.RedirectUri = "/";
var accessToken = new AuthenticationToken()
{
Name = OpenIdConnectParameterNames.AccessToken,
Value = Token
};
AuthenticationToken[] tokens = { accessToken };
auth.Properties.StoreTokens(tokens);
auth.Properties.IsPersistent = true;
await HttpContext.SignInAsync("Cookies", auth.Principal, auth.Properties);
So I want to say at this point everything is working fine.
Now back to the question. I wish to increase security by using Refresh Tokens, and this is where I hit the problem. Every tutorial or guide I google/bing that talks about Refresh tokens goes by the basses that I am using WASM or an API that I call. There are a few articles that say to use Local Storage but then that can bring up a whole new conversation of Local Storage vs Cookies vs Session, and I was hoping to stick with most of the built in stuff.
I understand that Server-Side issues with the HTTP context, hence why the login page is done on a .cshtml page and the fact that the cookies are set as HTTPOnly so JavaScript cannot access them.
So, is there away to do it?
Just to clarify, this is a Server-Side app only, no client apps or API's or anything else and I wish to stick with most of the built in stuff using cookies.
I am creating a console application that connects to Microsoft Graph using the Microsoft Graph API (as shown in https://github.com/microsoftgraph/console-csharp-connect-sample).
Everything is working fine, but I wonder if there is a way where I can authenticate a user (when I already know their user/password) without them needing to manually enter their credentials on the "Sing in to your account" window rendered on the desktop.
The idea is basically to run the application unattended, so there is no need for the user to be entering their credentials when the application starts. I canĀ“t find any relevant information on the subject.
Is that even possible?
EDIT
After following the link #DanSilver posted about geting access without a user, I tried the sample suggested in that link (https://github.com/Azure-Samples/active-directory-dotnet-daemon-v2). Although that is an MVC application that forces users to authenticate (precisely what I wanted to avoid) I have managed to use part of the authentication code in that sample with my console application. After giving authorization to the application manually through a request to https://login.microsoftonline.com/myTenantId/adminconsent I can create a GraphServiceClient in my console app that connects to Graph without user interaction. So I mark the answer as valid.
Just in case someone is in the same situation, the GraphServiceclient is created as:
GraphServiceClient graphServiceClientApplication = new GraphServiceClient("https://graph.microsoft.com/v1.0", new DelegateAuthenticationProvider(
async (requestMessage) =>
{
string clientId = "yourClientApplicationId";
string authorityFormat = "https://login.microsoftonline.com/{0}/v2.0";
string tenantId = "yourTenantId";
string msGraphScope = "https://graph.microsoft.com/.default";
string redirectUri = "msalXXXXXX://auth"; // Custom Redirect URI asigned in the Application Registration Portal in the native Application Platform
string clientSecret = "passwordGenerated";
ConfidentialClientApplication daemonClient = new ConfidentialClientApplication(clientId, String.Format(authorityFormat, tenantId), redirectUri, new ClientCredential(clientSecret), null, new TokenCache());
AuthenticationResult authResult = await daemonClient.AcquireTokenForClientAsync(new string[] { msGraphScope });
string token = authResult.AccessToken;
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", token);
}
));
One idea is using the "app only" authorization flow. The idea is that you can have long running apps access the Microsoft Graph without user authentication. The main difference is instead of the access token granting access to a particular user, it grants your app access to resources that you've consented to in advance. There will be no user login dialog and you can programmatically fetch access tokens to call the Graph API.
To reiterate that these tokens aren't for a particular user, consider making a GET request to 'https://graph.microsoft.com/v1.0/me'. This will return an error since the access token isn't for a particular user and "me" doesn't mean anything. Requests should be sent with full user ids "like graph.microsoft.com/users/someuser#contosos.com".
More information on this can be found at the Get access without a user documentation page.
Another idea is to let the user authenticate the first time they use your app and then store a refresh token. These tokens live longer (a few months IIRC) and then you won't need to prompt for user consent each time the app runs. Refresh tokens can be exchanged for access tokens that live 60 minutes and those can be used to call Graph API on behalf of users.
More info on refresh tokens: https://developer.microsoft.com/en-us/graph/docs/concepts/auth_v2_user#5-use-the-refresh-token-to-get-a-new-access-token
I did want to come back out here and share, since I ran into this problem yesterday, and the idea of granting read/write mailbox access for my application... to EVERYONE'S EMAIL BOX IN THE ENTIRE ORGANIZATION... was way over the top for my needs. (And that is exactly what happens when you start talking about granting Application level permissions instead of delegated permissions to your registered app).
It's a simple use case: I had a nightly process that needed to automate sending of emails from a shared mailbox using a traditional AD service account.
Thankfully... even though they are on the march to eliminate passwords (lol)... someone at Microsoft still recognizes my use case, and it's lack of apples-to-apples alternatives in Azure AD. There is still an extension method we can lean on to get the job done:
private AuthenticationContext authContext = null;
authContext = new AuthenticationContext("https://login.microsoftonline.com/ourmail.onmicrosoft.com",
new TokenCache());
result = authContext.AcquireTokenAsync("https://graph.microsoft.com/",
"12345678-1234-1234-1234-1234567890",
new UserPasswordCredential(
Environment.GetEnvironmentVariable("UID", EnvironmentVariableTarget.User),
Environment.GetEnvironmentVariable("UPD", EnvironmentVariableTarget.User)
)).Result;
You can replace those GetEnvironmentVariable calls with your Username (UID) and Password (UPD). I just stuff them in the environment variables of the service account so I didn't have to check anything into source control.
AcquireTokenAsync is an extension method made available from the Microsoft.IdentityModel.Clients.ActiveDirectory namespace. From there, it's a simple business to fire up a GraphClient.
string sToken = result.AccessToken;
Microsoft.Graph.GraphServiceClient oGraphClient = new GraphServiceClient(
new DelegateAuthenticationProvider((requestMessage) => {
requestMessage
.Headers
.Authorization = new AuthenticationHeaderValue("bearer", sToken);
return Task.FromResult(0);
}));
The last bit of magic was to add these permissions to Application registration I created in Azure AD (where that GUID came from). The application has be defined as a Public client (there's a radio button for that towards the bottom of the authentication tab). I added the following 5 DELEGATED permissions (NOT application permissions):
Microsoft Graph
1. Mail.ReadWrite.Shared
2. Mail.Send.Shared
3. User.Read
4. email
5. openid
Since user consents are actually blocked in our organization, another permissions admin had to review my application definition and then do an admin level grant of those rights, but once he did, everything lit up and worked like I needed: limited access by a service account to a single shared mailbox, with the actual security of that access being managed in Office 365 and not Azure AD.
This is more of a design/approach question...
I think I'm missing something here. We're building an Asp.Net MVC 5 web application and securing it with Azure AD using the following scenario:
https://azure.microsoft.com/en-us/documentation/articles/active-directory-authentication-scenarios/#web-browser-to-web-application
https://github.com/Azure-Samples/active-directory-dotnet-webapp-openidconnect
The token/cookie is an absolute expiry and expires after one hour. So what does that do for the user experience? Every hour they have to log back in no matter what? In our testing, when the user expires, the browser is redirected back to AD and the user prompted for credentials. This, of course, breaks any AJAX calls we have loading partial views and none of our DevExpress controls are stable as a result.
Based on the response to this SO post: MVC AD Azure Refresh Token via ADAL JavaScript Ajax and KnockoutJs
...what I'm seeing is expected? It seems to me like not a very viable solution for a cloud hosted line-of-business application where users are logged in and working all day.
Am I missing something? Or is this just not an ideal scenario for business apps?
We faced a similar set of problems, as well as the same thoughts about how you could use Azure AD with ASP.NET MVC in web apps with such a low session timeout (60 minutes).
The solution we came up with, that seems to be working (albeit with limited testing), is to have an iFrame on the page that we refresh every 5 minutes.
<iframe sandbox="allow-same-origin allow-scripts allow-popups allow-forms" id="refreshAuthenticationIframe" src="#Url.Action("CheckSessionTimeout", "Home", new { area = "" })" style="display:none;"></iframe>
The "CheckSessionTimeout" page is basically blank.
In a Javascript file referenced by the whole app, we have:
var pageLoadTime = moment();
setInterval(refreshAuthenticationCookies, 1000);
function refreshAuthenticationCookies() {
if (moment().diff(pageLoadTime, "seconds") > 300) {
document.getElementById("refreshAuthenticationIframe").contentDocument.location = "/Home/ForceSessionRefresh";
pageLoadTime = moment();
}
}
(NB: moment is a JS date/time library we use). On the Home controller, we have:
public ActionResult CheckSessionTimeout() => View();
public ActionResult ForceSessionRefresh()
{
HttpContext.GetOwinContext()
.Authentication.Challenge(new AuthenticationProperties { RedirectUri = "/Home/CheckSessiontimeout" },
OpenIdConnectAuthenticationDefaults.AuthenticationType);
return null;
}
I am not sure if any of that is the best way/approach. It's just the best we can do to fix up what seems like a set of difficult constraints with Azure AD and ASP.NET MVC apps (that are not SPAs, not using Web API but are using Ajax calls), relative to where we are coming from where none of this matters with on-premises apps doing Kerberos auth (and our user's expectations that session timeout is nothing they want to see or worry about).
There are two ways to handle this (at least this is how we are doing it in our application; it would be interesting to see what AD gurus have to say about this so that we can also fix it if it is not the right way to to do things):
General Approach - Use Refresh Token
When you get an access token from AD, today you get 3 things back - access token, access token expiry and a refresh token. What you do is cache all three of them in your application. Till the time access token is expired, you can simply use that access token. Once the token is expired, you can make use of refresh token to get a new access token. The method in ADAL you want to use for this purpose is AcquireTokenByRefreshToken.
Having said that, you should not take a hard dependency in your application on Refresh Token. Based on the best practices described here, a refresh token can expire or invalidated. Furthermore based on Vittorio's post, a refresh token is not even returned in ADAL version 3. So you may want to consider that.
Other Approach - Acquire Token Silently
Other approach you could take is acquire a new token silently on behalf of the user once the token expires. I believe this requires that a user must sign in manually at least once in your application and follow the OAuth2 flow. The method you want to use is AcquireTokenSilent.
Here's the pseudo code for our approach:
var now = DateTime.UtcNow.Ticks;
if (now <= tokenExpiry && !string.IsNullOrWhiteSpace(accessToken))
return accessToken;
var clientCredential = new ClientCredential(ClientId, ClientSecret);
var authContext = new AuthenticationContext(string.Format("{0}/{1}",
AzureActiveDirectorySignInEndpoint,
azureADTenantId));
AuthenticationResult authResult = null;
if (!string.IsNullOrWhiteSpace(refreshToken))
{
authResult = await authContext.AcquireTokenByRefreshTokenAsync(refreshToken,
clientCredential,
ADEndpoint);
}
else
{
authResult = await authContext.AcquireTokenSilentAsync(Endpoint,
clientCredential,
new UserIdentifier(userId, UserIdentifierType.UniqueId));
}
return authResult.AccessToken;//Also you may want to cache the token again
I referred this question and this MSDN post, but couldn't get the problem fixed.
Below code demonstrates how to perform the WAAD authentication using web browser:
AuthenticationContext auth = new AuthenticationContext("https://login.windows.net/" + myDomain);
AuthenticationResult result = auth.AcquireToken(resource, clientID, resourceAppIDURI);
This opens a browser and user is asked to enter the details which works fine.
However, I have a GUI client, which can take username/password/domain on its own. So the intention is to collect the details from GUI client and directly provide to the WAAD server and get the user authenticated.
How to do that?
Just looking at the overloads of AcquireToken(), I did get some clue (this can be wrong as well):
AuthenticationResult AcquireToken (string resource, Credential credential);
and
AuthenticationResult AcquireToken (string authorizationCode, string redirectUri, ClientCredential credential);
But I fail to understand, how to create the class ClientCredential (subclass of Credential). Important to note that this class belongs to the namespace Microsoft.WindowsAzure.ActiveDirectory.Authentication.
Below are its constructors:
ClientCredential(string clientId, SecureString secureClientSecret);
ClientCredential(string clientId, string clientSecret);
Searching online, I couldn't get much answers, I did get this link. But again the part of SecureString is a mystery to me. How the username/password/domain can be communicated using SecureString?
The older version of AAL supported this. However, it was removed about 2-3 months ago (as well as the sample showing it). Authentication of users now can only be achieved through a browser authentication window.
I'm not 100% sure, but I think the motivation is to create a standard/consistent login experience (for the end-users). If you're on a Windows 8 application, then the WebAuthenticationBroker handles this and users will recognize this for all Windows 8 applications. If you're a web application, then the AzureAD login page is presented and is recognizable.
The ClientCredential overload of AcquireToken that you found is for you to use a client id and secret key to authenticate (so that customers don't have to share their username and password).
Create a "web application" in the WAAD and the application will be given a client id guid. This is the first parameter in your ClientCredential() constructor.
To get the second parameter, add a key to your WAAD web application.
You might want to update to the latest version of AAL, which is now known as ADAL. NuGet link
I'm trying to create web page that access the (business) private calendar of the company and insert events if the time slot is available. Still I'm facing an authentication problem.
The API manual states that I should use an API key and Oauth2LeggedAuthenticator, so I did all this and the request that is fired is quite okey (it has a oauth token and such) But still the response is an exception with Invalid Credentials; Easy to say is that my credentials are wrong, still clientID, clientSecret and API Key are valid; I doubt the 2 last params of the 2legged authenticater, is this correct?
var provider = new NativeApplicationClient(GoogleAuthenticationServer.Description);
provider.ClientIdentifier = ClientCredentials.ClientID;
provider.ClientSecret = ClientCredentials.ClientSecret;
var authenticator =
new OAuth2LeggedAuthenticator(ClientCredentials.ClientID, ClientCredentials.ClientSecret, "myworkusername", "workdomain.com");
Google.Apis.Calendar.v3.CalendarService service = new Google.Apis.Calendar.v3.CalendarService(authenticator);
service.Key = ClientCredentials.ApiKey;
var result = service.CalendarList.List().Fetch();
Assert.IsTrue(result.Items.Count > 0);
NB: At the time of writing you can only used 2-legged authentication with Google Apps for Business/Eduction, this won't work on personal accounts as there's no way to get an OAuth 1.0 key/secret pair, you will have to use online authentication at least once (but you can use the out-of-browser option so you don't have to create a dedicated page).
Your code is correct apart from you don't need the first three lines relating to the NativeApplicationClient. This is most likely failing because you haven't properly set the OAuth keys, this causes 401s.
The other thing that causes 401s is using "matt#example.com" instead of "matt" as the username, the username is without including your domain.
To setup OAuth follow the instructions in this article from Google.
The most important parts to note are "Allow access to all APIs" must be unchecked and you have to individually grant access to all the APIs. If this hasn't been done you will get a 401 Invalid Credentials error. You then also need to turn those services on in the api console. If the api console step hasn't been done you will get a different error of 403 Daily Limit Exceeded.
This will cause you problems if you were previously relying on the "Allow access to all APIs" to use various services, you will have to grant them all individually as far as I understand it to use the v3 APIs. This seems to have been confirmed by google (4th reply by Nicolas Garnier) and is supposedly a bug, but that is an old post so it looks as if it's here to stay.
For reference once this has been done, this code will work, which in essence is the same as yours:
var auth = new OAuth2LeggedAuthenticator(domainName, consumerSecret, usernameWithoutDomain, domainName); //domainName is presently used as the OAuth ConsumerKey for Google's 2legged OAuth
var service = new CalendarService(auth);
service.Key = serviceKey;
var results = service.CalendarList.List().Fetch();
Console.WriteLine(results.Items.Count);
So in summary:
In Google Apps "Manage this Domain" > "Advanced Tools"
Using "Manage OAuth domain key" enable key, generate secret, uncheck "Allow access to all APIs".
Using "Manage third party OAuth Client access" enable the APIs you want access to using your domain as "Client Name" and the APIs you want to access e.g. "http://www.google.com/calendar/feeds/" for the calendar.
Then finally create a project in the API console, use the APIKey as the serviceKey in the above example and turn on the APIs you need to access.
I am answering this as I kept hitting this question when I was trying to find out why my code was constantly returning 401s. Hope this helps someone as the Google instructions are awful and scattered all over the place at the moment.