Skip to content

Commit

Permalink
Split auth service into auth and state services
Browse files Browse the repository at this point in the history
  • Loading branch information
DanielLarsenNZ committed Aug 19, 2018
1 parent 52be21c commit 1968c95
Show file tree
Hide file tree
Showing 5 changed files with 109 additions and 100 deletions.
20 changes: 16 additions & 4 deletions src/samples/SpotifyVue/Controllers/SpotifyController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,22 @@ public class SpotifyController : Controller
private readonly IArtistsApi _artists;
private readonly IPlayerApi _player;
private readonly IUserAccountsService _userAccounts;
private readonly SpotifyAuthService _authService;

public SpotifyController(IArtistsApi artists, IPlayerApi player, IUserAccountsService userAccounts, SpotifyAuthService authService)
private readonly UserAuthService _authService;
private readonly AuthStateService _stateService;

public SpotifyController
(
IArtistsApi artists,
IPlayerApi player,
IUserAccountsService userAccounts,
UserAuthService authService,
AuthStateService stateService
)
{
_artists = artists;
_userAccounts = userAccounts;
_authService = authService;
_stateService = stateService;
_player = player;
}

Expand Down Expand Up @@ -61,7 +70,7 @@ public SpotifyAuthorization Authorize()
if (userAuth != null && userAuth.Authorized) return MapToSpotifyAuthorization(userAuth);

// create a state value and persist it until the callback
string state = _authService.CreateAndStoreState(userId);
string state = _stateService.NewState(userId);

// generate an Authorization URL for the read and modify playback scopes
string url = _userAccounts.AuthorizeUrl(state, new[] { "user-read-playback-state", "user-modify-playback-state" });
Expand Down Expand Up @@ -89,6 +98,9 @@ public async Task<ContentResult> Authorize([FromQuery(Name = "state")] string st
var tokens = await _userAccounts.RequestAccessRefreshToken(userId, code);
var userAuth = _authService.SetUserAuthRefreshToken(userId, tokens);

//TODO: check state is valid
_stateService.ValidateState(state, userId);

// return an HTML result that posts a message back to the opening window and then closes itself.
return new ContentResult
{
Expand Down
29 changes: 29 additions & 0 deletions src/samples/SpotifyVue/Services/AuthStateService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System;
using System.Collections.Concurrent;

namespace SpotifyVue.Services
{
public class AuthStateService
{
/// A dictionary of state, userHash for verification of authorization callback
/// IRL this would be Redis, Table Storage or Cosmos DB
private readonly ConcurrentDictionary<string, string> _states = new ConcurrentDictionary<string, string>();

/// Creates and stores a new state value GUID
public string NewState(string userId)
{
// Store the state
string state = Guid.NewGuid().ToString("N");
_states.TryAdd(state, userId);
return state;
}

/// throws an exception if state is not found in dictionary, userId does not match
public void ValidateState(string state, string userId)
{
string value;
if (!_states.TryGetValue(state, out value)) throw new InvalidOperationException("Invalid State value");
if (value != userId) throw new InvalidOperationException("Invalid State value");
}
}
}
94 changes: 0 additions & 94 deletions src/samples/SpotifyVue/Services/SpotifyAuthService.cs

This file was deleted.

61 changes: 61 additions & 0 deletions src/samples/SpotifyVue/Services/UserAuthService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading.Tasks;
using SpotifyApi.NetCore;
using SpotifyApi.NetCore.Authorization;
using SpotifyVue.Models;

namespace SpotifyVue.Services
{
public class UserAuthService: IRefreshTokenProvider
{
// IRL this would be Table / Cosmos Db
private readonly ConcurrentDictionary<string, UserAuth> _userAuths = new ConcurrentDictionary<string, UserAuth>();

public UserAuth CreateUserAuth (string userId)
{
// Create the User Auth
var userAuth = GetUserAuth(userId);
userAuth = userAuth ?? new UserAuth
{
Authorized=false,
UserId = userId
};

InsertOrUpdateUserAuth(userId, userAuth);

return userAuth;
}

public Task<string> GetRefreshToken(string userId)
{
return Task.FromResult(GetUserAuth(userId).RefreshToken);
}

public UserAuth GetUserAuth(string userId)
{
UserAuth value = null;
_userAuths.TryGetValue(userId, out value);
return value;
}

public UserAuth SetUserAuthRefreshToken(string userId, BearerAccessRefreshToken tokens)
{
//TODO: No concurrency checking. Blows away any existing record
var userAuth = GetUserAuth(userId);
if (userAuth == null) throw new InvalidOperationException($"No valid User Auth record found for user hash \"{userId}\"");
userAuth.Authorized = true;
userAuth.RefreshToken = tokens.RefreshToken;
userAuth.Scopes = tokens.Scope;
InsertOrUpdateUserAuth(userId, userAuth);
return userAuth;
}

private void InsertOrUpdateUserAuth(string userId, UserAuth userAuth)
{
userAuth.EnforceInvariants();
_userAuths[userId] = userAuth;
}
}
}
5 changes: 3 additions & 2 deletions src/samples/SpotifyVue/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ public void ConfigureServices(IServiceCollection services)
services.AddSingleton(typeof(IArtistsApi), typeof(ArtistsApi));

// two service types, one implementation: https://stackoverflow.com/a/41812930/610731
services.AddSingleton(typeof(SpotifyAuthService), typeof(SpotifyAuthService));
services.AddSingleton(typeof(IRefreshTokenStore), x => x.GetService(typeof(SpotifyAuthService)));
services.AddSingleton(typeof(UserAuthService), typeof(UserAuthService));
services.AddSingleton(typeof(IRefreshTokenProvider), x => x.GetService(typeof(UserAuthService)));
services.AddSingleton(typeof(AuthStateService), typeof(AuthStateService));

services.AddSingleton(typeof(IUserAccountsService), typeof(UserAccountsService));
services.AddSingleton(typeof(IPlayerApi), typeof(PlayerApi));
Expand Down

0 comments on commit 1968c95

Please sign in to comment.