Browse Source

Merge pull request #211 from dotnet-architecture/xamarin

Xamarin client hybrid authentication flow
pull/223/head
David Britch 7 years ago
committed by GitHub
parent
commit
96a1a55426
11 changed files with 126 additions and 15 deletions
  1. +7
    -1
      src/Mobile/eShopOnContainers/eShopOnContainers.Core/GlobalSettings.cs
  2. +22
    -0
      src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Token/UserToken.cs
  3. +4
    -5
      src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Identity/IdentityService.cs
  4. +2
    -0
      src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/RequestProvider/IRequestProvider.cs
  5. +33
    -0
      src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/RequestProvider/RequestProvider.cs
  6. +10
    -0
      src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Token/ITokenService.cs
  7. +24
    -0
      src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Token/TokenService.cs
  8. +2
    -0
      src/Mobile/eShopOnContainers/eShopOnContainers.Core/ViewModels/Base/ViewModelLocator.cs
  9. +13
    -8
      src/Mobile/eShopOnContainers/eShopOnContainers.Core/ViewModels/LoginViewModel.cs
  10. +7
    -0
      src/Mobile/eShopOnContainers/eShopOnContainers.Core/eShopOnContainers.Core.csproj
  11. +2
    -1
      src/Mobile/eShopOnContainers/eShopOnContainers.UnitTests/project.json

+ 7
- 1
src/Mobile/eShopOnContainers/eShopOnContainers.Core/GlobalSettings.cs View File

@ -6,7 +6,6 @@
public const string MockTag = "Mock";
public const string DefaultEndpoint = "http://13.88.8.119";
private string _baseEndpoint;
private static readonly GlobalSetting _instance = new GlobalSetting();
@ -31,6 +30,10 @@
}
}
public string ClientId { get { return "xamarin"; }}
public string ClientSecret { get { return "secret"; }}
public string AuthToken { get; set; }
public string RegisterWebsite { get; set; }
@ -47,6 +50,8 @@
public string UserInfoEndpoint { get; set; }
public string TokenEndpoint { get; set; }
public string LogoutEndpoint { get; set; }
public string IdentityCallback { get; set; }
@ -61,6 +66,7 @@
BasketEndpoint = string.Format("{0}:5103", baseEndpoint);
IdentityEndpoint = string.Format("{0}:5105/connect/authorize", baseEndpoint);
UserInfoEndpoint = string.Format("{0}:5105/connect/userinfo", baseEndpoint);
TokenEndpoint = string.Format("{0}:5105/connect/token", baseEndpoint);
LogoutEndpoint = string.Format("{0}:5105/connect/endsession", baseEndpoint);
IdentityCallback = string.Format("{0}:5105/xamarincallback", baseEndpoint);
LogoutCallback = string.Format("{0}:5105/Account/Redirecting", baseEndpoint);


+ 22
- 0
src/Mobile/eShopOnContainers/eShopOnContainers.Core/Models/Token/UserToken.cs View File

@ -0,0 +1,22 @@
using Newtonsoft.Json;
namespace eShopOnContainers.Core.Models.Token
{
public class UserToken
{
[JsonProperty("id_token")]
public string IdToken { get; set; }
[JsonProperty("access_token")]
public string AccessToken { get; set; }
[JsonProperty("expires_in")]
public int ExpiresIn { get; set; }
[JsonProperty("token_type")]
public string TokenType { get; set; }
[JsonProperty("refresh_token")]
public string RefreshToken { get; set; }
}
}

+ 4
- 5
src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Identity/IdentityService.cs View File

@ -13,11 +13,10 @@ namespace eShopOnContainers.Core.Services.Identity
// Dictionary with values for the authorize request
var dic = new Dictionary<string, string>();
dic.Add("client_id", "xamarin");
dic.Add("client_secret", "secret");
dic.Add("response_type", "code id_token token");
dic.Add("client_id", GlobalSetting.Instance.ClientId);
dic.Add("client_secret", GlobalSetting.Instance.ClientSecret);
dic.Add("response_type", "code id_token");
dic.Add("scope", "openid profile basket orders offline_access");
dic.Add("redirect_uri", GlobalSetting.Instance.IdentityCallback);
dic.Add("nonce", Guid.NewGuid().ToString("N"));
@ -31,7 +30,7 @@ namespace eShopOnContainers.Core.Services.Identity
public string CreateLogoutRequest(string token)
{
if(string.IsNullOrEmpty(token))
if (string.IsNullOrEmpty(token))
{
return string.Empty;
}


+ 2
- 0
src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/RequestProvider/IRequestProvider.cs View File

@ -8,6 +8,8 @@ namespace eShopOnContainers.Core.Services.RequestProvider
Task<TResult> PostAsync<TResult>(string uri, TResult data, string token = "", string header = "");
Task<TResult> PostAsync<TResult>(string uri, string data, string clientId, string clientSecret);
Task DeleteAsync(string uri, string token = "");
}
}

+ 33
- 0
src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/RequestProvider/RequestProvider.cs View File

@ -61,6 +61,28 @@ namespace eShopOnContainers.Core.Services.RequestProvider
return result;
}
public async Task<TResult> PostAsync<TResult>(string uri, string data, string clientId, string clientSecret)
{
HttpClient httpClient = CreateHttpClient(string.Empty);
if (!string.IsNullOrWhiteSpace(clientId) && !string.IsNullOrWhiteSpace(clientSecret))
{
AddBasicAuthenticationHeader(httpClient, clientId, clientSecret);
}
var content = new StringContent(data);
content.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");
HttpResponseMessage response = await httpClient.PostAsync(uri, content);
await HandleResponse(response);
string serialized = await response.Content.ReadAsStringAsync();
TResult result = await Task.Run(() =>
JsonConvert.DeserializeObject<TResult>(serialized, _serializerSettings));
return result;
}
public async Task DeleteAsync(string uri, string token = "")
{
HttpClient httpClient = CreateHttpClient(token);
@ -90,6 +112,17 @@ namespace eShopOnContainers.Core.Services.RequestProvider
httpClient.DefaultRequestHeaders.Add(parameter, Guid.NewGuid().ToString());
}
private void AddBasicAuthenticationHeader(HttpClient httpClient, string clientId, string clientSecret)
{
if (httpClient == null)
return;
if (string.IsNullOrWhiteSpace(clientId) || string.IsNullOrWhiteSpace(clientSecret))
return;
httpClient.DefaultRequestHeaders.Authorization = new BasicAuthenticationHeaderValue(clientId, clientSecret);
}
private async Task HandleResponse(HttpResponseMessage response)
{
if (!response.IsSuccessStatusCode)


+ 10
- 0
src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Token/ITokenService.cs View File

@ -0,0 +1,10 @@
using eShopOnContainers.Core.Models.Token;
using System.Threading.Tasks;
namespace eShopOnContainers.Core.Services.Token
{
public interface ITokenService
{
Task<UserToken> GetTokenAsync(string code);
}
}

+ 24
- 0
src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Token/TokenService.cs View File

@ -0,0 +1,24 @@
using System.Net;
using System.Threading.Tasks;
using eShopOnContainers.Core.Services.RequestProvider;
using eShopOnContainers.Core.Models.Token;
namespace eShopOnContainers.Core.Services.Token
{
public class TokenService : ITokenService
{
private readonly IRequestProvider _requestProvider;
public TokenService(IRequestProvider requestProvider)
{
_requestProvider = requestProvider;
}
public async Task<UserToken> GetTokenAsync(string code)
{
string data = string.Format("grant_type=authorization_code&code={0}&redirect_uri={1}", code, WebUtility.UrlEncode(GlobalSetting.Instance.IdentityCallback));
var token = await _requestProvider.PostAsync<UserToken>(GlobalSetting.Instance.TokenEndpoint, data, GlobalSetting.Instance.ClientId, GlobalSetting.Instance.ClientSecret);
return token;
}
}
}

+ 2
- 0
src/Mobile/eShopOnContainers/eShopOnContainers.Core/ViewModels/Base/ViewModelLocator.cs View File

@ -8,6 +8,7 @@ using eShopOnContainers.Core.Services.OpenUrl;
using eShopOnContainers.Core.Services.RequestProvider;
using eShopOnContainers.Core.Services.Basket;
using eShopOnContainers.Core.Services.Identity;
using eShopOnContainers.Core.Services.Token;
using eShopOnContainers.Core.Services.Order;
using eShopOnContainers.Core.Services.User;
using Xamarin.Forms;
@ -53,6 +54,7 @@ namespace eShopOnContainers.Core.ViewModels.Base
builder.RegisterType<DialogService>().As<IDialogService>();
builder.RegisterType<OpenUrlService>().As<IOpenUrlService>();
builder.RegisterType<IdentityService>().As<IIdentityService>();
builder.RegisterType<TokenService>().As<ITokenService>();
builder.RegisterType<RequestProvider>().As<IRequestProvider>();
builder.RegisterType<LocationService>().As<ILocationService>().SingleInstance();


+ 13
- 8
src/Mobile/eShopOnContainers/eShopOnContainers.Core/ViewModels/LoginViewModel.cs View File

@ -1,6 +1,7 @@
using eShopOnContainers.Core.Helpers;
using eShopOnContainers.Core.Models.User;
using eShopOnContainers.Core.Services.Identity;
using eShopOnContainers.Core.Services.Token;
using eShopOnContainers.Core.Services.OpenUrl;
using eShopOnContainers.Core.Validations;
using eShopOnContainers.Core.ViewModels.Base;
@ -24,13 +25,16 @@ namespace eShopOnContainers.Core.ViewModels
private IOpenUrlService _openUrlService;
private IIdentityService _identityService;
private ITokenService _tokenService;
public LoginViewModel(
IOpenUrlService openUrlService,
IIdentityService identityService)
IIdentityService identityService,
ITokenService tokenService)
{
_openUrlService = openUrlService;
_identityService = identityService;
_tokenService = tokenService;
_userName = new ValidatableObject<string>();
_password = new ValidatableObject<string>();
@ -203,16 +207,15 @@ namespace eShopOnContainers.Core.ViewModels
private void Logout()
{
var authIdToken = Settings.AuthIdToken;
var logoutRequest = _identityService.CreateLogoutRequest(authIdToken);
if(!string.IsNullOrEmpty(logoutRequest))
if (!string.IsNullOrEmpty(logoutRequest))
{
// Logout
LoginUrl = logoutRequest;
}
if(Settings.UseMocks)
if (Settings.UseMocks)
{
Settings.AuthAccessToken = string.Empty;
Settings.AuthIdToken = string.Empty;
@ -233,12 +236,14 @@ namespace eShopOnContainers.Core.ViewModels
else if (unescapedUrl.Contains(GlobalSetting.Instance.IdentityCallback))
{
var authResponse = new AuthorizeResponse(url);
if (!string.IsNullOrWhiteSpace(authResponse.AccessToken))
if (!string.IsNullOrWhiteSpace(authResponse.Code))
{
if (authResponse.AccessToken != null)
var userToken = await _tokenService.GetTokenAsync(authResponse.Code);
string accessToken = userToken.AccessToken;
if (!string.IsNullOrWhiteSpace(accessToken))
{
Settings.AuthAccessToken = authResponse.AccessToken;
Settings.AuthAccessToken = accessToken;
Settings.AuthIdToken = authResponse.IdentityToken;
await NavigationService.NavigateToAsync<MainViewModel>();


+ 7
- 0
src/Mobile/eShopOnContainers/eShopOnContainers.Core/eShopOnContainers.Core.csproj View File

@ -169,6 +169,9 @@
<Compile Include="Converters\FirstValidationErrorConverter.cs" />
<Compile Include="Effects\EntryLineColorEffect.cs" />
<Compile Include="Behaviors\LineColorBehavior.cs" />
<Compile Include="Models\Token\UserToken.cs" />
<Compile Include="Services\Token\TokenService.cs" />
<Compile Include="Services\Token\ITokenService.cs" />
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
@ -262,6 +265,10 @@
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<Folder Include="Models\Token\" />
<Folder Include="Services\Token\" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>


+ 2
- 1
src/Mobile/eShopOnContainers/eShopOnContainers.UnitTests/project.json View File

@ -1,7 +1,8 @@
{
"dependencies": {
"Xamarin.Forms": "2.3.4.231",
"xunit": "2.2.0"
"xunit": "2.2.0",
"xunit.runner.console": "2.2.0"
},
"frameworks": {
".NETPortable,Version=v4.5,Profile=Profile111": {}


Loading…
Cancel
Save