C# – Use ASP.NET Identity External Authentication with desktop application

In previous posts we get to know with ASP.NET Identity and use it in many platforms such as Desktop or Android application. In all of them, I used local identity management system of ASP.NET Identity which connects to local database and manages users. So if users want to use our services, they have to register an account with our systems. They can’t use their Facebook or Google account to login to our system. Today I’ll show you how to extend our services by enabling OAuth 2 so that the users can authenticate themselves with their Facebook, Google+, Microsoft or Twitter accounts. You may find a lot of tutorials out there in Internet for integrating this external authentication within a web application. Therefore I wouldn’t like to write the same thing again but I will use a WPF application as my client and authenticate with ASP.NET Identity service over external authentication of Google+. This concept can be also used for mobile application such as Android, IOS or Windows Phone.

1. Prerequisites

– Very important : we’re going to use OAuth 2 for authentication. If you have no idea what OAuth 2 is or how it works. I suggest to search for some good tutorials and read them before continuing. I won’t explain OAuth 2 in details.
– WPF client uses MVVM pattern with Caliburn.Micro. If you don’t know what it is then read this post first C# – WPF MVVM DataBinding 2014.
– For enhancing UI, I use Community Edition of Syncfusion WPF. If you like them too, you can get complete package at following link https://www.syncfusion.com/products/communitylicense .
– I will reuse the web service from this post C# – ASP.NET Web API and ASP.NET Identity and extend it for supporting OAuth 2. That post says more in details how to create web service with ASP.NET Identity. Maybe you should read it too.

2. Web service

2.1 Google+ API

As I mentioned above, we’ll use Google account to authenticate users with our web service. In other words, our apps should be able to talk with Google API. So we have to register our apps with Google first.
– Go to https://console.developers.google.com/
– Create a project if you don’t have any.

Create project

– Click on your project link to go to project control panel. On the left menu, click on APIs, be sure that Google+ API is enabled. If not, you can enable it from the list API Library

Google+ API

2.2 Google Credentials

– In Google Console Developers, go to Credentials and Create new Client ID.

Create new Client ID

– Create a new Web application Client Id as following.

Web application client id

– You should replace http://localhost:1952 with your web service URL. Now a new generated client id with Client ID and Client secret will appear in your control panel.

New Client ID

– Write down your Client ID and Client secret, you’ll need it later.

2.3 Activate Google OAuth in web service

– In web service, find your Startup class and activate Google Authentication by uncommenting the code block and fill it with your generated Client ID and Client secret above.

app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions()
{
	ClientId = "1047953342030-qbmtjc8c5updevo50ssa60u1rscms4s4.apps.googleusercontent.com",
	ClientSecret = "iLChQq4vDbp6pT1a-TvMvn3y"
});

– Now your web service is able to use Google as external authentication service.
– I also have prepared a sample web service for you to test, you can access it over this URL.

http://aspnetwebapiandaspnetidentity.apphb.com

3. WPF client

– The WPF client has a very simple UI for demonstrating the registration and login process.

WPF Client

3.1 OAuth 2

As I said in the prerequisites, I won’t go in details to explain how OAuth 2 works. But the following image of OAuth 2 Flows will give you an overview how the authentication progress looks like.

OAuth 2 Flow

Use this flow to understand the steps below in next section.

3.2 OAuth 2 with ASP.NET Identity

3.2.1 Register

To register an user with ASP.NET Identity you have to follow these steps. These steps bases on OAuth 2 Flow above.

1. Make a get to
api/Account/ExternalLogins?returnUrl=%2F&generateState=true
to see which providers are currently being supported.

Supported External Authentication Provider

2. Select the provider you want to use, read the value of Url and combine it with your web service URL. Browse to this URL in your web browser. For example, in our sample case, it should be.

http://aspnetwebapiandaspnetidentity.apphb.com/api/Account/ExternalLogin?provider=Google&response_type=token&client_id=self&redirect_uri=http%3A%2F%2Faspnetwebapiandaspnetidentity.apphb.com%2F&state=9t4WMN_ETdLZjK2IhxA3nbkKxcx6I__hipz8pFFPv8Y1

When you browse to this Url, the user will be redirected to Google web site so that he can authenticate himself to Google service.

Authenticate with Google

User should approve that our apps can access their profile.

Accept OAuth 2

After user gives permissions to the app, Google will redirect user back to our web service with a temporary access_token. For example

http://aspnetwebapiandaspnetidentity.apphb.com/#access_token=Bg2l_5FhjWDVUP9Wa57flBJr2L4KRBHjJLygIs5DSE_ihgpd33TETfxvscz0EOzhXb0F-MdyaWbX5xG6Bce1DHhozy7e099lCYiiAWx7tPawjjN17UYVN8gyaJjhnkWOZUEs-KplOzaV3yJaxXZS3ftpQCx-TrY6rv1Qlavlw1dSrT2j5qeYvwB7feXPWGweUocCJ75WxrgLf8RqCbfM5NvfIcu5GWoPsfEEfbyMjI3WPya-0EE9BIsdjiHBSog5_5QVeyxJx_BD2r1Z3Pe7HtSs7BzpA7A10pOCEQQLnR6vZ-m8SH7PqqVZk97ouTeM0zxLnRILLvArR2xX_PH7dA&token_type=bearer&expires_in=1209600&state=kq6QfnvQwjb_5eIWKjQldHjp18vwBJTBVl5cuO8JjYY1

3. Take this access_token, add it to HTTP Headers. This access_token is very important, don’t forget to use it on every request you send to your web services. To check if all steps above work, you can send a GET (with access_token) to
api/Account/UserInfo.

UserInfo

The HasRegistered must be false right now because we still don’t register the user with our service yet but the LoginProvider must be correct, in our case, it’s Google.

4. Now make a POST to
api/Account/RegisterExternal
to register user. Remember your temporary access_token should be there in the Authorization header. After this post, user is officially registered in your system.

3.2.3 Authenticate

To authenticate a registered user, you just need to repeat the steps above till step 3 when you get access_token. After that, you can use it in Authorization header and access the protected resources

4. Source code

Follow the steps above, the code listing below shows its implementation in real code.

private async Task GetSupportedAccounts()
{
	responseMessage = await httpClient.GetAsync("api/Account/ExternalLogins?returnUrl=%2F&generateState=true");
	supportedExternalAccounts = await responseMessage.Content.ReadAsAsync<List<ExternalLoginViewModel>>();
}

public async Task Start()
{
	BrowserViewModel browserViewModel = new BrowserViewModel();
	string googleAuth = supportedExternalAccounts.Where(x => x.Name.Equals("Google")).FirstOrDefault().Url;
	browserViewModel.Url = serviceUrl.Substring(0, serviceUrl.Length - 1) + googleAuth;
	windowManager.ShowDialog(browserViewModel);

	if (!string.IsNullOrWhiteSpace(browserViewModel.AccessToken))
	{
		var httpHandler = new HttpClientHandler() { CookieContainer = browserViewModel.CookieContainer };
		httpClient = new HttpClient(httpHandler) { BaseAddress = new Uri(serviceUrl) };
		httpClient.DefaultRequestHeaders.Add("Authorization", string.Format("Bearer {0}", browserViewModel.AccessToken));
		responseMessage = await httpClient.GetAsync("api/Account/UserInfo");
		responseContent = await responseMessage.Content.ReadAsStringAsync();
		UserInfoViewModel userInfoViewModel = await responseMessage.Content.ReadAsAsync<UserInfoViewModel>();
		Protocol += responseContent + Environment.NewLine;
		bool isNewRegistered = false;
		if (!userInfoViewModel.HasRegistered)
		{
			responseMessage = await httpClient.PostAsJsonAsync("api/Account/RegisterExternal", new RegisterExternalBindingModel() { Email = userInfoViewModel.Email });
			if (responseMessage.IsSuccessStatusCode)
			{
				Protocol += "Register successfully" + Environment.NewLine;
				isNewRegistered = true;
			}
		}

		if (isNewRegistered)
		{
			await Start();
		}

		responseMessage = await httpClient.GetAsync("api/Account/UserInfo");
		responseContent = await responseMessage.Content.ReadAsStringAsync();
		Protocol += responseContent + Environment.NewLine;

		responseMessage = await httpClient.GetAsync("api/Values");
		responseContent = await responseMessage.Content.ReadAsStringAsync();

		Protocol += responseContent + Environment.NewLine;
	}
}

The web browser dialog contains a WebBrowser control so that user can enter his username and password and accept the permission requests.

<syncfusion:ChromelessWindow x:Class="WpfClient_for_OAuth.Views.BrowserView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:syncfusion="http://schemas.syncfusion.com/wpf"
        WindowStartupLocation="CenterScreen" TitleTextAlignment="Left" ShowIcon="False"
        Title="BrowserView" Height="700" Width="700" Loaded="ChromelessWindow_Loaded">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition ></RowDefinition>
            <RowDefinition Height="60"></RowDefinition>
        </Grid.RowDefinitions>
		<WebBrowser  Navigated="webBrowser_Navigated" Name="webBrowser" />
		<TextBox IsReadOnly="True" Grid.Row="1" Name="Url" TextWrapping="Wrap"></TextBox>
	</Grid>
</syncfusion:ChromelessWindow>

5. Conclusion

ASP.NET Identity does support OAuth 2. The configuration is easy to make things work. If you have a ASP.NET web application, it will be integrated directly inside of the app. If you have a mobile or desktop application, you can follow the steps above to register a new user or authenticate an existed one.

Source code: https://bitbucket.org/hintdesk/dotnet-asp.net-web-api-and-asp.net-identity

6. Notices

– When publishing to AppHabor set this setting “aspnet:UseHostHeaderForRequestUrl” to true. Otherwise, redirect_uri will be formatted as url:port which cause invalid_request from Google.

<add key="aspnet:UseHostHeaderForRequestUrl" value="true" />

– To clear Internet Explorer cache, go to Internet Options –> General –> Settings –> View files –> Delete all files there. Use Delete button on General tab is not enough.

12 thoughts on “C# – Use ASP.NET Identity External Authentication with desktop application”

  1. Hi,

    I am trying to call the web api from WPF client and you article really helped. I would like to store the access code obtained for a registered user and and use it for future calls in the applications. I will be doing many asynchronous calls. Any help in caching and using the token for later use will be helpful. Thanks.

  2. I’ve used your tutorial and I’ve noticed that when you call api/Account/UserInfo you get back

    Email=Full Name

    My issue is that I need the actual email address of the user because registerExternal will return an error otherwise. Yet in your code above, /UserInfo returns your full name, which you insert into your request body message and seemingly get a successful result. Am I doing something wrong, or did I miss something?

    Thanks

  3. @Rex: You need to add Email to ExternalLoginData and remember to add EmailClaim to cookie. I updated the code so that we can get email as username back so that registration works.

  4. This seems to be giving me trouble with Facebook. Is there something different that needs to be done for that particular provider?

  5. @Rex: Facebook and Twitter don’t return Email in OAuth2.
    Let’s force your users to enter their Email in your app and use those Email for registering with External Account.

  6. Great article and has helped me understand the flow but I’m struggling with the redirect to Facebook from ExternalLogin due to CORS issues. I get the error ‘XMLHttpRequest cannot load https://www.facebook.com/dialog/oauth…… No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin ‘https://localhost:8080′ is therefore not allowed access.’

    Any ideas? Much appreciated!

  7. @Angie: I struggled with this also. Figured out that you need to redirect to the ExternalLogin url instead of sending AJAX get call to it.

  8. Im always getting “Authorization has been denied for this request.”

    when registering external user using the RegisterExternal.

    I am using the token provided by LinkedIn which is my external provider.

    Any clue why?

Leave a Reply

Your email address will not be published. Required fields are marked *