Listing Subscriptions and Logic Apps from .NET#

For most people, using PowerShell scripts/cmdlets to access Azure and get information about subscriptions and resources (e.g. Logic Apps) will be enough.

But what if you want to do it yourself from .NET?

Luckily there are some good management libraries you can use. However, there aren't a huge amount of samples out there on how to use those management libraries.

So I thought I'd provide a guide on how to do the following: Log into Azure, get a list of subscriptions, select a subscription, and then get a list of LogicApps associated with that subscription.

The second part of this I covered in an earlier post on how to use the Logic Apps Management SDK. But I soon discovered that the Azure Management Libraries and the Logic Apps Management Library use a slightly different credential format.

The Demo App

Here's what the app we're creating is going to look like:

1) How to login

Normally when you login to Azure, you have to know what AAD Tenant to authenticate against (see the sidebar below for an overview of AAD and tenants).

However, Azure has the concept of a multi-tenant login i.e. a login where you don't know which AAD Tenant you're using: so you login using the common tenant.

When you login, you have to specify the ID of the client you're using. Normally in AAD you'd set up a client in your directory, and then authenticate against the access rights given to that client.

This obviously wouldn't work for a common tenant, so in the special case of the common tenant, Azure has a list of common clients you can use. One of these is the PowerShell client: when we use this client id, Azure assumes that we're a PowerShell script trying to authenticate against Azure (you can see this in the login window, as it will say Sign in to Microsoft Azure PowerShell in the window title).

Logging in using the common tenant gives us limited permissions, but one of the things we *can* do is get a list of the subscriptions that the current user can access.

Logging in is as simple as running this code:

string TokenAudience = "https://management.core.windows.net/" ;

string ClientRedirectUri = "urn:ietf:wg:oauth:2.0:oob" ;

string ClientId = "1950a258-227b-4e31-a9cf-717495945fc2" ;

string EnableMagicCookie = "site_id=501358&display=popup" ;

string BaseLoginUrl = "https://login.windows.net/{0}" ;

string TenantId = "common" ;

AuthenticationResult result = null ;

AuthenticationContext authenticationContext = new AuthenticationContext ( string .Format(BaseLoginUrl, TenantId), true , TokenCache .DefaultShared);

try

{

    result = authenticationContext.AcquireToken(

                TokenAudience,

                ClientId,

                 new Uri (ClientRedirectUri),

                 PromptBehavior .RefreshSession,

                 UserIdentifier .AnyUser,

                EnableMagicCookie);

}

catch ( AdalException )

{

    result = null ;

}

return result;

Some explanations:

  • TokenAudience is what we're authenticating against i.e. the service we wish to use - in this case, we want to use the Management SDK.
  • ClientRedirectUri is set to the standard redirect URI for OAuth2.0 calls (this can also be used to redirect to a particular resource upon successful login).
  • ClientId is the special ID for the Azure PowerShell client.
  • EnableMagicCookie is an extra query parameter added to the end of the web request used to open the login window, that forces the window to try and use the Email Address method of determining if the user is an Org Account or Microsoft Account (see sidebar later for more on this).
  • BaseLoginUrl is the login URL to use to login to Azure (with a placeholder for the tenant id).
  • TenantId is the id of the AAD tenant we wish to use (which in this case is Common, as we don't know which tenant to use just yet).

What is returned to us is an AuthenticationResult object that will have a JWT (aka Bearer Token) in the AccessToken property (see the sidebar later for an explanation of JWTs/Bearer Tokens).

This AccessToken is what we pass to our Management Library method calls, and later to our LogicApps SDK methods calls.

2) How to get a list of subscriptions

Luckily, with the Management Library, this is really easy:

CancellationToken canToken = new CancellationToken ();

TokenCloudCredentials cred = new TokenCloudCredentials (commonTenantCredentials.AccessToken);

SubscriptionClient client = CloudContext .Clients.CreateSubscriptionClient(cred);

SubscriptionListOperationResponse response = await client.Subscriptions.ListAsync(canToken);

// Project subscriptions into a list of SubscriptionWrapper instances

List < SubscriptionWrapper > subscriptions = response.Subscriptions.Select

(

sub =>

new SubscriptionWrapper (sub)

).ToList();

There are three main parts to the code above:

  1. We need to create a new TokenCloudCredentials instance, which is what we pass to the management library - we instantiate this using our JWT token stored in the AccessToken property of the AuthenticationResult object we received in the last step.
  2. We create a new SubscriptionClient (using our credentials) and call Subscriptions.ListAsync to get a list of subscriptions.
  3. We project that list of subscriptions into a new class called SubscriptionWrapper , which is used with our ListBox - the SubscriptionWrapper class just overrides the ToString() method to provide a better description in our list box.

3) How to get a list of Logic Apps

Getting a list of LogicApps is simple too - all we need is our AccessToken , and the ID of a subscription.

We get the user to select a subscription from the list we obtained in the previous step, and then execute this code:

using ( LogicManagementClient client = new LogicManagementClient (

new TokenCredentials (credentials.AccessToken)))

{

client.SubscriptionId = subscription.SubscriptionId;

Page < Workflow > workflowPage = client.Workflows.ListBySubscription();

List < Workflow > workflows = workflowPage.ToList();

}

Here, we're doing the following:

  1. Creating a new LogicManagementClient , passing it a new TokenCredentials instance (instantiated using our AccessToken ).
  2. Setting the SubscriptionId on the client to the ID of the subscription the user selected.
  3. Getting a list of LogicApps (known as Workflows in the SDK) by calling Workflows.ListBySubscription() .

So far, so good.

But if you were to try and execute the above code using the common tenant AccessToken we obtained in Step 1) you may find yourself getting this error:

The full error text is:

"The access token is from the wrong issuer 'https://sts.windows.net//'. It must match the tenant 'https://sts.windows.net//' associated with this subscription. Please use the authority (URL) 'https://login.windows.net/' to get the token. Note, if the subscription is transferred to another tenant there is no impact to the services, but information about new tenant could take time to propagate (up to an hour). If you just transferred your subscription and see this error message, please try back later."

The reason for this is simple: we authenticated against the common tenant, but now we're trying access data from a subscription which belongs to a separate tenant - and we don't have an AccessToken for this new tenant.

What we have to do in this case is acquire a new AccessToken (a JWT) for the same user and client ID, but authorising against the tenant for the subscription we selected.

i.e. we have an AccessToken , but it's a common tenant AccessToken , and therefore is limited in what is authorised: to work with resources that are specific to a particular subscription, we now need an AccessToken for that specific subscription and tenant.

[Note: if we didn't need to supply a list of subscriptions, and new the subscription id and tenant id all along, we wouldn't even need to sign in using the common tenant].

To do this, we just need to run the code in Step 1) again, but this time instead of using a TenantId of "common", we use the TenantId of the subscription the User selected.

Running the code from Step 1) again will cause the login box to appear again, but in most cases it will flash up blank, and then disappear, as long as it detects that your common tenant credentials are valid for the new tenant.

Note: You may notice that when you use the portal, and you log in, and then change your subscription, you don't see a login box: that's because there's a way to refresh your current AccessToken for the new tenant without going via the standard login route. I'll cover this in a later post.

4) How to logoff

Logging off should be really simple. But again, like most things in Azure when you have multiple accounts, it's not.

To log off for a given AccessToken (i.e. a given login) you have to do the following:

  1. Clear the TokenCache.
  2. Clear any cookies created (e.g. if you ticked the "keep me logged in" option).
  3. Make a call to a logoff URL that looks like this: https://login.windows.net/{0}/oauth2/logout where {0} is the id of the tenant you're logging off from.

The thing is, you can be logged into multiple tenants at the same time.

Personally, I think the logout functionality is a bit messy, although I suspect it's just that it needs to be surfaced properly in an API or REST call.

This is the code that needs to be executed. The AuthenticationObject needs to be the one you created to login with (i.e. the one you put the TenantId into). The code for clearing the Cookies comes from the source code for the UserTokenProvider in GitHub ( https://github.com/Azure/azure-sdk-for-net/blob/master/src/Authentication/Common.Authentication/Authentication/UserTokenProvider.cs - hat tip to Poul K. Sørensen for finding it first!):

string LogoutUrlBase = "https://login.windows.net/{0}/oauth2/logout" ;

authenticationContext.TokenCache.Clear();

new WebClient ().OpenRead( string .Format(LogoutUrlBase, "common" ));

NativeMethods .InternetSetOption( IntPtr .Zero, NativeMethods .INTERNET_OPTION_END_BROWSER_SESSION, IntPtr .Zero, 0);

private static class NativeMethods

{

internal const int INTERNET_OPTION_END_BROWSER_SESSION = 42;

[ DllImport ( "wininet.dll" , SetLastError = true )]

internal static extern bool InternetSetOption( IntPtr hInternet, int dwOption, IntPtr lpBuffer,

int lpdwBufferLength);

}

And that's it.

Well, there's a bit more: you should cache the AuthenticationObjects and AccessTokens for each Tenant you login to so you don't login more than once.

Here's a link to the sample code - it's a VS2015 project. It's not production quality code (hey, it's a WinForm app! How retro!) but it does the job and gives you an idea of how to do this yourself. Also, to keep the size down, I didn't include the NuGet packages, so when you first open it it will attempt to download them all (or go to Tools/NuGet Packaage Manager/Manage NuGet Packages for Solution to download them) - it won't compile without them:

DemoGetSubscriptionsAndLogicApps v1.0.zip (18.29 KB)

Any problems, let me know - I can't guarantee it'll work for all scenarios.

Sidebar

Here's some useful information that should help you understand how all of this works under the covers.

What "logging into Azure" means

When you log into Azure, you're logging into Azure Active Directory (AAD) - either one you host (in your subscription) or a common one (e.g. O365).

Technically, you're using an AAD Tenant: A tenant is an instance of Azure Active Directory just for your organisation (basically the same meaning when we talk about tenants in multi-threaded programming): when you create an O365 Enterprise subscription you get assigned a tenant, as you do when you create an Azure subscription [I'm simplifying this a great deal, as it's really a rather complex subject - see this post here https://msdn.microsoft.com/en-us/library/azure/jj573650.aspx , or see Vittorio's post here , or Brady's post here for more detail).

When you create a personal Azure Subscription, an AAD tenant is created for you, and within that AAD is a default directory - that's right, it's an Azure Active Directory Directory!

The default directory will use a name related to your Microsoft Account Name e.g. if your Microsoft Account is dan@acme.com, your default directory will be danacmecom.onmicrosoft.com .

You can create multiple AAD Directories from within the management portal - an AAD Directory doesn't belong to any subscription.

A subscription can only use one directory at a time - you can see what directory is being used by going to the Settings view in the old portal.

Note : if you have an Enterprise O365 account, you can link the AAD tenant for your O365 account to your Azure subscription, and then tell that subscription to use the O365 directory for authentication - which means your corporate users can log in to the Azure subscription.

Because we're using the management libraries, we're using the OAuth 2.0 protocol for logging in - which means that when we successfully, authenticate, we're given a Bearer token in JWT form (pronounced "jot"), which is stored in a cookie locally against the id of the client we authenticated against.

A JWT (JSON Web Token - see here for more info, and for a decoder: http://jwt.io/ ) is a string comprised of three Base64 encoded sections, separated by full stops/period (.). You can decode these section to see what they contain - the important bits are who has been authenticated, and when the JWT expires.

When we authenticate (using the Azure login window) we're passed one of these Bearer tokens, and we have to then transform it into a Credentials object for use by the Management libraries.

Note that if you were using the REST interface (which is what the Management libraries use underneath) you'd be passing the Bearer token in the HTTP header for each request.

Two types of Azure Account

There are currently two types of Azure account:

  • Microsoft Accounts - these are the old Hotmail/Passport/Windows Live ID accounts. They're a personal account, and they're to be used to login to consumer-based Microsoft services e.g. Outlook.com, personal O365 subscriptions, Xbox Live etc. They're also the default way of creating Azure subscriptions (until recently, you had to use a Microsoft Account to create an Azure Subscription, or as the Account Administrator). They're denoted by the Microsoft four-colour flag icon.
  • Organisational Accounts (aka work or school accounts) - these are corporate/enterprise accounts, used to log into Enterprise services e.g. 0365 under an Enterprise License. They use the domain of your organisation, and you can sync these accounts with an on-premise AD server. They're denoted by an icon that looks like a corporate photo id on a lanyard (see more here: https://azure.microsoft.com/en-gb/documentation/articles/sign-up-organization/ ).

Why is this important? Azure needs to know what type of account you have, so it can work out which AAD Tenant to authenticate you against.

When you attempt to login to an Azure resource e.g. the management portal, you'll sometimes see this screen:

(it used to have a password box under it, and when you tabbed out of the email address box, it would start doing something, but recently they've started phasing out that version and started using the version above).

The reason this screen exists is to allow Azure to work out if you have an Org Account or a Microsoft Account - you're then directed to the appropriate login screen: Org Accounts can customise their login screen.

If you happen to have an Org Account and a Microsoft Account with the same name, then you'll see this screen next:

This is Azure's way of saying that it can't work out which of the two accounts you wish to use.

Some services only allow you to use a Microsoft Account to login - this will be obvious on the login screen:

The bane of those with multiple accounts: Auto Logon

One thing that those people with multiple Azure accounts (both Microsoft Accounts and Org Accounts ) hate with a passion (well, I do!!) is auto logon: you go a secured resource (e.g. Azure Portal), click the Sign In button (or just type in the page URL), and instead of being asked which account you want to use, you're just logged in with what seems like a random account.

Why does this happen? It's to do with how Azure AD has grown over time, and how something that is designed to make life easier for people with single accounts tends to make it more complex for those with multiple accounts.

Firstly, there's the "Keep me signed in" feature: if you see this checkbox on a login screen, and tick it, a cookie is created locally which stores your authentication token.

Next time you go access a secured resource or click the SignIn button, as long as the authentication token is still valid, then you're automatically logged in using that account.

But… if you then login using another account, and *don't* tick the same option, and then close your browser, and then try and login again, instead of logging you in with the previously used account, you're logged in with the original account (i.e. the one you ticked the option for).

It gets more complex when you realise that whilst only a single Microsoft Account can be marked for auto-login at a single time, multiple Corporate Accounts can be marked for auto-login at the same time.

I mention this because when it comes to logging in from code, you can actually control this functionality and (hopefully) give your end user a more pleasant login experience.

How to login to Azure if you don't know the tenant

With all the discussion above, it may not be obvious on how to login to Azure if you don't know which AAD Tenant to authenticate against.

The solution is that Azure provides for the concept of a common tenant: the common tenant indicates that you don't know which tenant should be used (and you don't actually care): Azure will log you in against the relevant tenant for the user signing in. After the user has logged in, you can get the tenant details (should you need them).

Prompt Behavior

In Step 1, where we login, note the use of a variable called promptBehavior : this lets us control whether or not we wish to see the login window if we're already logged in.

This value is an enum called PromptBehavior , and has the following values and meanings:

Member name

Description

Always

The user will be prompted for credentials even if there is a token that meets the requirements already in the cache.

Auto

Acquire token will prompt the user for credentials only when necessary. If a token that meets the requirements is already cached then the user will not be prompted.

Never

The user will not be prompted for credentials. If prompting is necessary then the AcquireToken request will fail.

RefreshSession

Re-authorizes (through displaying webview) the resource usage, making sure that the resulting access token contains updated claims. If user logon cookies are available, the user will not be asked for credentials again and the logon dialog will dismiss automatically.

In our case, we want to use RefreshSession when we first login (in case we have a token that can be reused) but want to set it to Always when we Log off (so that we're prompted to login next time we login). There are some flaws in this plan: since we're using the PowerShell client ID, if you logged into PowerShell recently, and chosen the "keep me logged in" option, then when you run this tool, you'll be auto logged in as the user you logged in to PowerShell with. The only way around this (that I know of at the moment) is to use a different client id.

Monday, 16 November 2015 15:15:00 (GMT Standard Time, UTC+00:00) #    Comments [0]  |  Trackback

 

Comments are closed.
All content © 2017, Daniel Probert
On this page
This site
Calendar
<2017 May>
SunMonTueWedThuFriSat
30123456
78910111213
14151617181920
21222324252627
28293031123
45678910
Archives
Sitemap
Blogroll OPML
Disclaimer

Powered by: newtelligence dasBlog 2.3.12105.0

The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

Send mail to the author(s) E-mail

Theme design by Jelle Druyts


Pick a theme: