| using ProtoBuf; | |||||
| namespace GrpcShared | |||||
| { | |||||
| [ProtoContract] | |||||
| public class AuthParams | |||||
| { | |||||
| [ProtoMember(1)] | |||||
| public string ClientId { get; set; } | |||||
| [ProtoMember(2)] | |||||
| public string RedirectURI { get; set; } | |||||
| [ProtoMember(3)] | |||||
| public string Scope { get; set; } | |||||
| } | |||||
| } |
| using ProtoBuf; | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Linq; | |||||
| using System.Text; | |||||
| using System.Threading.Tasks; | |||||
| namespace GrpcShared.DTO.Auth | |||||
| { | |||||
| [ProtoContract] | |||||
| public class AuthRequest | |||||
| { | |||||
| [ProtoMember(1)] | |||||
| public string ClientId { get; set; } | |||||
| [ProtoMember(2)] | |||||
| public string ClientSecret { get; set; } | |||||
| [ProtoMember(3)] | |||||
| public string RedirectURI { get; set; } | |||||
| [ProtoMember(4)] | |||||
| public string ResponseType { get; set; } = "track"; | |||||
| [ProtoMember(5)] | |||||
| public string Scope { get; set; } | |||||
| [ProtoMember(6)] | |||||
| public string ShowDialog{ get; set; } | |||||
| } | |||||
| } |
| using ProtoBuf; | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.ComponentModel; | |||||
| using System.Linq; | |||||
| using System.Text; | |||||
| using System.Threading.Tasks; | |||||
| namespace GrpcShared.DTO.Auth | |||||
| { | |||||
| [ProtoContract] | |||||
| public class CodeRequest | |||||
| { | |||||
| [ProtoMember(1)] | |||||
| public string? ClientId { get; set; } | |||||
| [ProtoMember(2)] | |||||
| public string? ClientSecret { get; set; } | |||||
| [ProtoMember(3)] | |||||
| public string? RedirectURI { get; set; } | |||||
| [ProtoMember(4)] | |||||
| public string ResponseType { get; set; } = "code"; | |||||
| [ProtoMember(5)] | |||||
| public string? Scope { get; set; } | |||||
| [ProtoMember(6)] | |||||
| [DefaultValue(true)] | |||||
| public bool ShowDialog { get; set; } = true; | |||||
| } | |||||
| } |
| using ProtoBuf; | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Linq; | |||||
| using System.Text; | |||||
| using System.Threading.Tasks; | |||||
| namespace GrpcShared.DTO.Auth | |||||
| { | |||||
| [ProtoContract] | |||||
| public class CodeResponse | |||||
| { | |||||
| [ProtoMember(1)] | |||||
| public string Code { get; set; } | |||||
| } | |||||
| } |
| using ProtoBuf; | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Linq; | |||||
| using System.Text; | |||||
| using System.Threading.Tasks; | |||||
| namespace GrpcShared.DTO.Auth | |||||
| { | |||||
| [ProtoContract] | |||||
| public class TokenRequest | |||||
| { | |||||
| [ProtoMember(1)] | |||||
| public string grant_type { get; set; } = "authorization_code"; | |||||
| [ProtoMember(2)] | |||||
| public string? code { get; set; } | |||||
| [ProtoMember(3)] | |||||
| public string? redirect_uri { get; set; } | |||||
| } | |||||
| } |
| namespace GrpcShared.DTO.Auth | namespace GrpcShared.DTO.Auth | ||||
| { | { | ||||
| [ProtoContract] | [ProtoContract] | ||||
| public class AuthResponse | |||||
| public class TokenResponse | |||||
| { | { | ||||
| [ProtoMember(1)] | [ProtoMember(1)] | ||||
| public string AccessToken { get; set; } | |||||
| public string? AccessToken { get; set; } | |||||
| [ProtoMember(2)] | [ProtoMember(2)] | ||||
| public string RefreshToken{ get; set; } | |||||
| public string? RefreshToken{ get; set; } | |||||
| [ProtoMember(3)] | |||||
| public int? ExpiresIn { get; set; } | |||||
| } | } | ||||
| } | } |
| [Service] | [Service] | ||||
| public interface IAuthService | public interface IAuthService | ||||
| { | { | ||||
| Task<CodeResponse> GetCode(AuthRequest request); | |||||
| Task<AuthResponse> GetAccessToken(CodeResponse code); | |||||
| Task<AuthParams> GetAuthParams(); | |||||
| Task<TokenResponse> GetAccessToken(TokenRequest code); | |||||
| Task<CodeRequest> GetAuthParams(); | |||||
| //Task<ClientSecrets> GetClientSecrets(); | |||||
| } | } | ||||
| } | } |
| <PackageReference Include="Grpc.AspNetCore" Version="2.40.0" /> | <PackageReference Include="Grpc.AspNetCore" Version="2.40.0" /> | ||||
| <PackageReference Include="Grpc.AspNetCore.Web" Version="2.47.0" /> | <PackageReference Include="Grpc.AspNetCore.Web" Version="2.47.0" /> | ||||
| <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="6.0.8" /> | <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="6.0.8" /> | ||||
| <PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> | |||||
| <PackageReference Include="protobuf-net.Grpc" Version="1.0.171" /> | <PackageReference Include="protobuf-net.Grpc" Version="1.0.171" /> | ||||
| <PackageReference Include="protobuf-net.Grpc.AspNetCore" Version="1.0.152" /> | <PackageReference Include="protobuf-net.Grpc.AspNetCore" Version="1.0.152" /> | ||||
| <PackageReference Include="protobuf-net.Grpc.AspNetCore.Reflection" Version="1.0.152" /> | <PackageReference Include="protobuf-net.Grpc.AspNetCore.Reflection" Version="1.0.152" /> |
| using Microsoft.AspNetCore.Server.Kestrel.Core; | using Microsoft.AspNetCore.Server.Kestrel.Core; | ||||
| using ProtoBuf.Grpc.Server; | using ProtoBuf.Grpc.Server; | ||||
| using Microsoft.Extensions.Options; | using Microsoft.Extensions.Options; | ||||
| using GrpcShared.DTO.Auth; | |||||
| var builder = WebApplication.CreateBuilder(args); | var builder = WebApplication.CreateBuilder(args); | ||||
| #if DEBUG | #if DEBUG | ||||
| builder.Services.AddOptions(); | builder.Services.AddOptions(); | ||||
| // Additional configuration is required to successfully run gRPC on macOS. | // Additional configuration is required to successfully run gRPC on macOS. | ||||
| // For instructions on how to configure Kestrel and gRPC clients on macOS, visit https://go.microsoft.com/fwlink/?linkid=2099682 | // For instructions on how to configure Kestrel and gRPC clients on macOS, visit https://go.microsoft.com/fwlink/?linkid=2099682 | ||||
| builder.Services.Configure<AuthParams>(builder.Configuration.GetSection("AuthParams")); | |||||
| builder.Services.Configure<CodeRequest>(builder.Configuration.GetSection("AuthParams")); | |||||
| builder.Services.AddControllersWithViews(); | builder.Services.AddControllersWithViews(); | ||||
| builder.Services.AddRazorPages(); | builder.Services.AddRazorPages(); | ||||
| builder.Services.AddGrpc(); | builder.Services.AddGrpc(); | ||||
| builder.Services.AddCodeFirstGrpc(); | builder.Services.AddCodeFirstGrpc(); | ||||
| builder.Services.AddCodeFirstGrpcReflection(); | builder.Services.AddCodeFirstGrpcReflection(); | ||||
| //call spotify api | |||||
| builder.Services.AddHttpClient(); | |||||
| var app = builder.Build(); | var app = builder.Build(); | ||||
| app.UseHttpsRedirection(); | app.UseHttpsRedirection(); | ||||
| //run blazor project by running grpc server | |||||
| app.UseBlazorFrameworkFiles(); | app.UseBlazorFrameworkFiles(); | ||||
| app.UseStaticFiles(); | app.UseStaticFiles(); | ||||
| app.UseRouting(); | app.UseRouting(); | ||||
| //for http2 -> http conversion | |||||
| app.UseGrpcWeb(); | app.UseGrpcWeb(); | ||||
| app.MapRazorPages(); | app.MapRazorPages(); |
| //using IdentityProvider.Protos.AuthService; | //using IdentityProvider.Protos.AuthService; | ||||
| using Grpc.Net.Client; | |||||
| using GrpcShared; | using GrpcShared; | ||||
| using GrpcShared.DTO.Auth; | using GrpcShared.DTO.Auth; | ||||
| using GrpcShared.Interfaces; | using GrpcShared.Interfaces; | ||||
| using Microsoft.Extensions.Options; | using Microsoft.Extensions.Options; | ||||
| using Microsoft.Net.Http.Headers; | |||||
| using Newtonsoft.Json; | |||||
| using System.Net.Http.Headers; | |||||
| using System.Text; | |||||
| using System.Text.Json; | |||||
| namespace IdentityProvider.Services | namespace IdentityProvider.Services | ||||
| { | { | ||||
| public class AuthService : IAuthService | public class AuthService : IAuthService | ||||
| { | { | ||||
| private readonly ILogger<AuthService> _logger; | private readonly ILogger<AuthService> _logger; | ||||
| private readonly AuthParams _params; | |||||
| public AuthService(ILogger<AuthService> logger, IOptions<AuthParams> options ) | |||||
| private readonly CodeRequest _params; | |||||
| private readonly IHttpClientFactory _httpClientFactory; | |||||
| public AuthService(ILogger<AuthService> logger, IOptions<CodeRequest> options, IHttpClientFactory httpClientFactory) | |||||
| { | { | ||||
| _logger = logger; | _logger = logger; | ||||
| _params = options.Value; | _params = options.Value; | ||||
| _httpClientFactory = httpClientFactory; | |||||
| } | } | ||||
| public Task<AuthResponse> GetAccessToken(CodeResponse code) | |||||
| public async Task<TokenResponse> GetAccessToken(TokenRequest tokenRequest) | |||||
| { | { | ||||
| throw new NotImplementedException(); | |||||
| } | |||||
| var http = _httpClientFactory.CreateClient(); | |||||
| public Task<CodeResponse> GetCode(AuthRequest request) | |||||
| { | |||||
| throw new NotImplementedException(); | |||||
| string url = "https://accounts.spotify.com/api/token"; | |||||
| http.BaseAddress = new Uri(url); | |||||
| //get client id and secret from appsettings, convert to base64 and set as header | |||||
| var secrets = await GetAuthParams(); | |||||
| byte[] contentType = Encoding.UTF8.GetBytes($"{secrets.ClientId}:{secrets.ClientSecret}"); | |||||
| http.DefaultRequestHeaders.Add(HeaderNames.Authorization, "Basic " + Convert.ToBase64String(contentType)); | |||||
| //ACCEPT HEADER | |||||
| http.DefaultRequestHeaders.Accept.Add( | |||||
| new MediaTypeWithQualityHeaderValue("application/json")); | |||||
| //BODY | |||||
| var requestBody = new Dictionary<string, string>(); | |||||
| requestBody["grant_type"] = tokenRequest.grant_type; | |||||
| requestBody["code"] = tokenRequest.code!; | |||||
| requestBody["redirect_uri"] = secrets.RedirectURI!; | |||||
| var response = await http.PostAsync(url, new FormUrlEncodedContent(requestBody)); | |||||
| var contents = JsonConvert.DeserializeObject<TokenResponse>(await response.Content.ReadAsStringAsync()); | |||||
| return await Task.FromResult(new TokenResponse | |||||
| { | |||||
| AccessToken = contents!.AccessToken, | |||||
| RefreshToken = contents!.RefreshToken, | |||||
| ExpiresIn = contents!.ExpiresIn | |||||
| }); | |||||
| } | } | ||||
| public async Task<AuthParams> GetAuthParams() | |||||
| public async Task<CodeRequest> GetAuthParams() | |||||
| { | { | ||||
| var authParams = new AuthParams { | |||||
| var authParams = new CodeRequest | |||||
| { | |||||
| ClientId = _params.ClientId, | ClientId = _params.ClientId, | ||||
| RedirectURI = _params.RedirectURI, | RedirectURI = _params.RedirectURI, | ||||
| Scope =_params.Scope }; | |||||
| Scope = _params.Scope, | |||||
| ClientSecret = _params.ClientSecret | |||||
| }; | |||||
| return await Task.FromResult(authParams); | return await Task.FromResult(authParams); | ||||
| } | } | ||||
| } | } | ||||
| } | } |
| "AuthParams": { | "AuthParams": { | ||||
| "ClientId": "83e1d09876b049c4bb1953185a4b3bfb", | "ClientId": "83e1d09876b049c4bb1953185a4b3bfb", | ||||
| "RedirectURI": "https://localhost:44342/callback", | "RedirectURI": "https://localhost:44342/callback", | ||||
| "Scope": "user-read-currently-playing user-read-email user-library-modify user-top-read" | |||||
| "Scope": "user-read-currently-playing user-read-email user-library-modify user-top-read", | |||||
| "ClientSecret": "ea752433d0774fad87fab5c1ee788c8d" | |||||
| } | } | ||||
| } | } |
| @page "/callback" | |||||
| @using NemAnBlazor.Services.Interfaces | |||||
| @inject NavigationManager NavigationMgr | |||||
| @inject IAuthClientService AuthService | |||||
| <PageTitle>Callback page</PageTitle> | |||||
| <p role="status">Current count: @currentCount</p> | |||||
| <button class="btn btn-primary" @onclick="IncrementCount">Click me</button> | |||||
| @code { | |||||
| private int currentCount = 0; | |||||
| private void IncrementCount() | |||||
| { | |||||
| currentCount++; | |||||
| } | |||||
| protected override async Task OnInitializedAsync() | |||||
| { | |||||
| string url = NavigationMgr.Uri; | |||||
| //code is the only parameter in the url | |||||
| string code = url.Split("=")[1]; | |||||
| string redirectURI = "https://localhost:44342/"; //ovo promeni da se storuje negde na neko univerzalno mesto | |||||
| var response = await AuthService.GetAccessToken(new GrpcShared.DTO.Auth.TokenRequest { code = code, redirect_uri = redirectURI}); | |||||
| //store access token in local storage | |||||
| } | |||||
| } |
| @page "/counter" | |||||
| <PageTitle>Counter</PageTitle> | |||||
| <h1>Counter</h1> | |||||
| <p role="status">Current count: @currentCount</p> | |||||
| <button class="btn btn-primary" @onclick="IncrementCount">Click me</button> | |||||
| @code { | |||||
| private int currentCount = 0; | |||||
| private void IncrementCount() | |||||
| { | |||||
| currentCount++; | |||||
| } | |||||
| } |
| protected override async Task OnInitializedAsync() | protected override async Task OnInitializedAsync() | ||||
| { | { | ||||
| //var response = await SearchService.GetListSearchAsync(new GrpcShared.DTO.Search.SearchRequest() { Query="venom", Type = "track"}); | //var response = await SearchService.GetListSearchAsync(new GrpcShared.DTO.Search.SearchRequest() { Query="venom", Type = "track"}); | ||||
| AuthParams authParams = await AuthService.GetAuthParams(); | |||||
| CodeRequest authParams = await AuthService.GetAuthParams(); | |||||
| // await AuthService.GetAccessToken(new CodeResponse{ Code = "hello"}); | // await AuthService.GetAccessToken(new CodeResponse{ Code = "hello"}); | ||||
| AuthRequest request = new() { ResponseType = "code", Scope = authParams.Scope, ClientId = authParams.ClientId, RedirectURI = authParams.RedirectURI}; | |||||
| string url = $"https://accounts.spotify.com/en/authorize?client_id={request.ClientId}&redirect_uri={request.RedirectURI}&response_type={request.ResponseType}&scope={request.Scope}&show_dialog=true"; | |||||
| string url = $"https://accounts.spotify.com/en/authorize?client_id={authParams.ClientId}&redirect_uri={authParams.RedirectURI}&response_type={authParams.ResponseType}&scope={authParams.Scope}&show_dialog={authParams.ShowDialog}"; | |||||
| NavigationManager.NavigateTo(url); | NavigationManager.NavigateTo(url); | ||||
| } | } |
| { | { | ||||
| _serviceClient = grpcChannel.CreateGrpcService<IAuthService>(); | _serviceClient = grpcChannel.CreateGrpcService<IAuthService>(); | ||||
| } | } | ||||
| public async Task<AuthResponse> GetAccessToken(CodeResponse code) | |||||
| public async Task<TokenResponse> GetAccessToken(TokenRequest request) | |||||
| { | { | ||||
| return await _serviceClient.GetAccessToken(code); | |||||
| return await _serviceClient.GetAccessToken(request); | |||||
| } | } | ||||
| public async Task <AuthParams> GetAuthParams() | |||||
| public async Task<CodeRequest> GetAuthParams() | |||||
| { | { | ||||
| return await _serviceClient.GetAuthParams(); | return await _serviceClient.GetAuthParams(); | ||||
| } | } | ||||
| public Task<CodeResponse> GetCode(AuthRequest request) | |||||
| { | |||||
| throw new NotImplementedException(); | |||||
| } | |||||
| } | } | ||||
| } | } |
| { | { | ||||
| public interface IAuthClientService | public interface IAuthClientService | ||||
| { | { | ||||
| Task<CodeResponse> GetCode(AuthRequest request); | |||||
| Task<AuthResponse> GetAccessToken(CodeResponse code); | |||||
| Task<AuthParams> GetAuthParams(); | |||||
| Task<TokenResponse> GetAccessToken(TokenRequest tokenRequest); | |||||
| Task<CodeRequest> GetAuthParams(); | |||||
| } | } | ||||
| } | } |
| </NavLink> | </NavLink> | ||||
| </div> | </div> | ||||
| <div class="nav-item px-3"> | <div class="nav-item px-3"> | ||||
| <NavLink class="nav-link" href="counter"> | |||||
| <NavLink class="nav-link" href="callback"> | |||||
| <span class="oi oi-plus" aria-hidden="true"></span> Counter | <span class="oi oi-plus" aria-hidden="true"></span> Counter | ||||
| </NavLink> | </NavLink> | ||||
| </div> | </div> |
| @using Microsoft.JSInterop | @using Microsoft.JSInterop | ||||
| @using NemAnBlazor | @using NemAnBlazor | ||||
| @using NemAnBlazor.Shared | @using NemAnBlazor.Shared | ||||
| @using System.Web |