Skip to content

Commit 5db62c1

Browse files
authored
Merge pull request dotnet-architecture#211 from dotnet-architecture/xamarin
Xamarin client hybrid authentication flow
2 parents 4ed070d + 7ee4887 commit 5db62c1

11 files changed

Lines changed: 126 additions & 15 deletions

File tree

src/Mobile/eShopOnContainers/eShopOnContainers.Core/GlobalSettings.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ public class GlobalSetting
66
public const string MockTag = "Mock";
77
public const string DefaultEndpoint = "http://13.88.8.119";
88

9-
109
private string _baseEndpoint;
1110
private static readonly GlobalSetting _instance = new GlobalSetting();
1211

@@ -31,6 +30,10 @@ public string BaseEndpoint
3130
}
3231
}
3332

33+
public string ClientId { get { return "xamarin"; }}
34+
35+
public string ClientSecret { get { return "secret"; }}
36+
3437
public string AuthToken { get; set; }
3538

3639
public string RegisterWebsite { get; set; }
@@ -47,6 +50,8 @@ public string BaseEndpoint
4750

4851
public string UserInfoEndpoint { get; set; }
4952

53+
public string TokenEndpoint { get; set; }
54+
5055
public string LogoutEndpoint { get; set; }
5156

5257
public string IdentityCallback { get; set; }
@@ -61,6 +66,7 @@ private void UpdateEndpoint(string baseEndpoint)
6166
BasketEndpoint = string.Format("{0}:5103", baseEndpoint);
6267
IdentityEndpoint = string.Format("{0}:5105/connect/authorize", baseEndpoint);
6368
UserInfoEndpoint = string.Format("{0}:5105/connect/userinfo", baseEndpoint);
69+
TokenEndpoint = string.Format("{0}:5105/connect/token", baseEndpoint);
6470
LogoutEndpoint = string.Format("{0}:5105/connect/endsession", baseEndpoint);
6571
IdentityCallback = string.Format("{0}:5105/xamarincallback", baseEndpoint);
6672
LogoutCallback = string.Format("{0}:5105/Account/Redirecting", baseEndpoint);
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using Newtonsoft.Json;
2+
3+
namespace eShopOnContainers.Core.Models.Token
4+
{
5+
public class UserToken
6+
{
7+
[JsonProperty("id_token")]
8+
public string IdToken { get; set; }
9+
10+
[JsonProperty("access_token")]
11+
public string AccessToken { get; set; }
12+
13+
[JsonProperty("expires_in")]
14+
public int ExpiresIn { get; set; }
15+
16+
[JsonProperty("token_type")]
17+
public string TokenType { get; set; }
18+
19+
[JsonProperty("refresh_token")]
20+
public string RefreshToken { get; set; }
21+
}
22+
}

src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Identity/IdentityService.cs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,10 @@ public string CreateAuthorizationRequest()
1313

1414
// Dictionary with values for the authorize request
1515
var dic = new Dictionary<string, string>();
16-
dic.Add("client_id", "xamarin");
17-
dic.Add("client_secret", "secret");
18-
dic.Add("response_type", "code id_token token");
16+
dic.Add("client_id", GlobalSetting.Instance.ClientId);
17+
dic.Add("client_secret", GlobalSetting.Instance.ClientSecret);
18+
dic.Add("response_type", "code id_token");
1919
dic.Add("scope", "openid profile basket orders offline_access");
20-
2120
dic.Add("redirect_uri", GlobalSetting.Instance.IdentityCallback);
2221
dic.Add("nonce", Guid.NewGuid().ToString("N"));
2322

@@ -31,7 +30,7 @@ public string CreateAuthorizationRequest()
3130

3231
public string CreateLogoutRequest(string token)
3332
{
34-
if(string.IsNullOrEmpty(token))
33+
if (string.IsNullOrEmpty(token))
3534
{
3635
return string.Empty;
3736
}

src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/RequestProvider/IRequestProvider.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ public interface IRequestProvider
88

99
Task<TResult> PostAsync<TResult>(string uri, TResult data, string token = "", string header = "");
1010

11+
Task<TResult> PostAsync<TResult>(string uri, string data, string clientId, string clientSecret);
12+
1113
Task DeleteAsync(string uri, string token = "");
1214
}
1315
}

src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/RequestProvider/RequestProvider.cs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,28 @@ public async Task<TResult> PostAsync<TResult>(string uri, TResult data, string t
6161
return result;
6262
}
6363

64+
public async Task<TResult> PostAsync<TResult>(string uri, string data, string clientId, string clientSecret)
65+
{
66+
HttpClient httpClient = CreateHttpClient(string.Empty);
67+
68+
if (!string.IsNullOrWhiteSpace(clientId) && !string.IsNullOrWhiteSpace(clientSecret))
69+
{
70+
AddBasicAuthenticationHeader(httpClient, clientId, clientSecret);
71+
}
72+
73+
var content = new StringContent(data);
74+
content.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");
75+
HttpResponseMessage response = await httpClient.PostAsync(uri, content);
76+
77+
await HandleResponse(response);
78+
string serialized = await response.Content.ReadAsStringAsync();
79+
80+
TResult result = await Task.Run(() =>
81+
JsonConvert.DeserializeObject<TResult>(serialized, _serializerSettings));
82+
83+
return result;
84+
}
85+
6486
public async Task DeleteAsync(string uri, string token = "")
6587
{
6688
HttpClient httpClient = CreateHttpClient(token);
@@ -90,6 +112,17 @@ private void AddHeaderParameter(HttpClient httpClient, string parameter)
90112
httpClient.DefaultRequestHeaders.Add(parameter, Guid.NewGuid().ToString());
91113
}
92114

115+
private void AddBasicAuthenticationHeader(HttpClient httpClient, string clientId, string clientSecret)
116+
{
117+
if (httpClient == null)
118+
return;
119+
120+
if (string.IsNullOrWhiteSpace(clientId) || string.IsNullOrWhiteSpace(clientSecret))
121+
return;
122+
123+
httpClient.DefaultRequestHeaders.Authorization = new BasicAuthenticationHeaderValue(clientId, clientSecret);
124+
}
125+
93126
private async Task HandleResponse(HttpResponseMessage response)
94127
{
95128
if (!response.IsSuccessStatusCode)
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using eShopOnContainers.Core.Models.Token;
2+
using System.Threading.Tasks;
3+
4+
namespace eShopOnContainers.Core.Services.Token
5+
{
6+
public interface ITokenService
7+
{
8+
Task<UserToken> GetTokenAsync(string code);
9+
}
10+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using System.Net;
2+
using System.Threading.Tasks;
3+
using eShopOnContainers.Core.Services.RequestProvider;
4+
using eShopOnContainers.Core.Models.Token;
5+
6+
namespace eShopOnContainers.Core.Services.Token
7+
{
8+
public class TokenService : ITokenService
9+
{
10+
private readonly IRequestProvider _requestProvider;
11+
12+
public TokenService(IRequestProvider requestProvider)
13+
{
14+
_requestProvider = requestProvider;
15+
}
16+
17+
public async Task<UserToken> GetTokenAsync(string code)
18+
{
19+
string data = string.Format("grant_type=authorization_code&code={0}&redirect_uri={1}", code, WebUtility.UrlEncode(GlobalSetting.Instance.IdentityCallback));
20+
var token = await _requestProvider.PostAsync<UserToken>(GlobalSetting.Instance.TokenEndpoint, data, GlobalSetting.Instance.ClientId, GlobalSetting.Instance.ClientSecret);
21+
return token;
22+
}
23+
}
24+
}

src/Mobile/eShopOnContainers/eShopOnContainers.Core/ViewModels/Base/ViewModelLocator.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using eShopOnContainers.Core.Services.RequestProvider;
99
using eShopOnContainers.Core.Services.Basket;
1010
using eShopOnContainers.Core.Services.Identity;
11+
using eShopOnContainers.Core.Services.Token;
1112
using eShopOnContainers.Core.Services.Order;
1213
using eShopOnContainers.Core.Services.User;
1314
using Xamarin.Forms;
@@ -53,6 +54,7 @@ public static void RegisterDependencies(bool useMockServices)
5354
builder.RegisterType<DialogService>().As<IDialogService>();
5455
builder.RegisterType<OpenUrlService>().As<IOpenUrlService>();
5556
builder.RegisterType<IdentityService>().As<IIdentityService>();
57+
builder.RegisterType<TokenService>().As<ITokenService>();
5658
builder.RegisterType<RequestProvider>().As<IRequestProvider>();
5759
builder.RegisterType<LocationService>().As<ILocationService>().SingleInstance();
5860

src/Mobile/eShopOnContainers/eShopOnContainers.Core/ViewModels/LoginViewModel.cs

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using eShopOnContainers.Core.Helpers;
22
using eShopOnContainers.Core.Models.User;
33
using eShopOnContainers.Core.Services.Identity;
4+
using eShopOnContainers.Core.Services.Token;
45
using eShopOnContainers.Core.Services.OpenUrl;
56
using eShopOnContainers.Core.Validations;
67
using eShopOnContainers.Core.ViewModels.Base;
@@ -24,13 +25,16 @@ public class LoginViewModel : ViewModelBase
2425

2526
private IOpenUrlService _openUrlService;
2627
private IIdentityService _identityService;
28+
private ITokenService _tokenService;
2729

2830
public LoginViewModel(
2931
IOpenUrlService openUrlService,
30-
IIdentityService identityService)
32+
IIdentityService identityService,
33+
ITokenService tokenService)
3134
{
3235
_openUrlService = openUrlService;
3336
_identityService = identityService;
37+
_tokenService = tokenService;
3438

3539
_userName = new ValidatableObject<string>();
3640
_password = new ValidatableObject<string>();
@@ -203,16 +207,15 @@ private void Register()
203207
private void Logout()
204208
{
205209
var authIdToken = Settings.AuthIdToken;
206-
207210
var logoutRequest = _identityService.CreateLogoutRequest(authIdToken);
208211

209-
if(!string.IsNullOrEmpty(logoutRequest))
212+
if (!string.IsNullOrEmpty(logoutRequest))
210213
{
211214
// Logout
212215
LoginUrl = logoutRequest;
213216
}
214217

215-
if(Settings.UseMocks)
218+
if (Settings.UseMocks)
216219
{
217220
Settings.AuthAccessToken = string.Empty;
218221
Settings.AuthIdToken = string.Empty;
@@ -233,12 +236,14 @@ private async Task NavigateAsync(string url)
233236
else if (unescapedUrl.Contains(GlobalSetting.Instance.IdentityCallback))
234237
{
235238
var authResponse = new AuthorizeResponse(url);
236-
237-
if (!string.IsNullOrWhiteSpace(authResponse.AccessToken))
239+
if (!string.IsNullOrWhiteSpace(authResponse.Code))
238240
{
239-
if (authResponse.AccessToken != null)
241+
var userToken = await _tokenService.GetTokenAsync(authResponse.Code);
242+
string accessToken = userToken.AccessToken;
243+
244+
if (!string.IsNullOrWhiteSpace(accessToken))
240245
{
241-
Settings.AuthAccessToken = authResponse.AccessToken;
246+
Settings.AuthAccessToken = accessToken;
242247
Settings.AuthIdToken = authResponse.IdentityToken;
243248

244249
await NavigationService.NavigateToAsync<MainViewModel>();

src/Mobile/eShopOnContainers/eShopOnContainers.Core/eShopOnContainers.Core.csproj

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,9 @@
169169
<Compile Include="Converters\FirstValidationErrorConverter.cs" />
170170
<Compile Include="Effects\EntryLineColorEffect.cs" />
171171
<Compile Include="Behaviors\LineColorBehavior.cs" />
172+
<Compile Include="Models\Token\UserToken.cs" />
173+
<Compile Include="Services\Token\TokenService.cs" />
174+
<Compile Include="Services\Token\ITokenService.cs" />
172175
</ItemGroup>
173176
<ItemGroup>
174177
<None Include="app.config" />
@@ -262,6 +265,10 @@
262265
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
263266
</EmbeddedResource>
264267
</ItemGroup>
268+
<ItemGroup>
269+
<Folder Include="Models\Token\" />
270+
<Folder Include="Services\Token\" />
271+
</ItemGroup>
265272
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
266273
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
267274
<PropertyGroup>

0 commit comments

Comments
 (0)