jensenkd / plex-api

.NET Core SDK for Plex Media Server
MIT License
86 stars 27 forks source link

OAuth Example #6

Closed bbakermmc closed 4 years ago

bbakermmc commented 4 years ago

Do you have an OAuth Example? So users can feel a bit more secure.

bbakermmc commented 4 years ago

Here is an example of oauth

public class OAuthResponse
    {
        public string Url { get; set; }
        public string Id { get; set; }
        public string Code { get; set; }
    }

public class PlexOAuthClient
    {
        private readonly string _clientId = "PlexApps";
        private readonly string _device = "PlexApps(Web)";
        private readonly HttpClient _httpClient = new HttpClient();
        private readonly string _platform = "Web";
        private readonly string _product = "PlexApps";

        public PlexOAuthClient()
        {
            _httpClient.DefaultRequestHeaders.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/json"));
            _httpClient.DefaultRequestHeaders.Add("X-Plex-Product", _product);
            _httpClient.DefaultRequestHeaders.Add("X-Plex-Platform", _platform);
            _httpClient.DefaultRequestHeaders.Add("X-Plex-Device", _device);
            _httpClient.DefaultRequestHeaders.Add("X-Plex-Client-Identifier", _clientId);
        }

        public async Task<OAuthResponse> GetOAuthUrl(string redirectUrl)
        {
            using (var request =
                new HttpRequestMessage(HttpMethod.Post, "https://plex.tv/api/v2/pins.json?strong=true"))
            {
                var resp = await _httpClient.SendAsync(request);
                var respStr = await resp.Content.ReadAsStringAsync();
                dynamic respJson = JsonConvert.DeserializeObject(respStr);
                return new OAuthResponse
                {
                    Url =
                        $"https://app.plex.tv/auth#?context[device][product]={_product}&context[device][environment]=bundled&context[device][layout]=desktop&context[device][platform]={_platform}&context[device][device]={_device}&clientID={_clientId}&forwardUrl={redirectUrl}&code={respJson.code}",
                    Id = respJson.id,
                    Code = respJson.code
                };
            }
        }

        public async Task<string> GetAuthToken(string oauthId)
        {
            using (var request = new HttpRequestMessage(HttpMethod.Get, "https://plex.tv/api/v2/pins/" + oauthId))
            {
                var resp = await _httpClient.SendAsync(request);
                var respStr = await resp.Content.ReadAsStringAsync();
                dynamic respJson = JsonConvert.DeserializeObject(respStr);
                return respJson.authToken;
            }
        }
    }

public class PlexLoginController : Controller
    {
        private readonly HttpClient _httpClient = new HttpClient();
        private readonly PlexOAuthClient _plexOAuthClient;

        public PlexLoginController(PlexOAuthClient plexOAuthClient)
        {
            _plexOAuthClient = plexOAuthClient;
        }

        public async Task<IActionResult> IndexAsync()
        {
            var redirectUrl = Url.Action("PlexReturn", "PlexLogin", null, Request.Scheme);
            var oauthUrl = await _plexOAuthClient.GetOAuthUrl(redirectUrl);
            HttpContext.Session.SetString("PlexOauthId", oauthUrl.Id);
            return Redirect(oauthUrl.Url);
        }

        public async Task<IActionResult> PlexReturn()
        {
            var oauthId = HttpContext.Session.GetString("PlexOauthId");
            if (string.IsNullOrEmpty(oauthId))
                throw new Exception("Missing oauth ID.");
            var authToken = await _plexOAuthClient.GetAuthToken(oauthId);
            if (string.IsNullOrEmpty(authToken))
                throw new Exception("Plex auth failed.");
            HttpContext.Session.Remove("PlexOauthId");
            HttpContext.Session.SetString("PlexKey", authToken);

            return Redirect("/plex");
        }
    }
jensenkd commented 4 years ago

I should be able to get this implemented soon. I like the idea!

jensenkd commented 4 years ago

So I haven't had time to build a web app to test this yet..but here is how you would create a pin and get the URL to send your user to in your Blazor app:


 public void Test_CreateOAuthPin()
  {
       var plexApi = ServiceProvider.GetService<IPlexClient>();
       var result1 = plexApi.CreateOAuthPin().Result;

       string product = "";
       string platform = "";
       string device = "";
       string clientId = "";
       string redirectUrl = "";

       string url =
                $"https://app.plex.tv/auth#?context[device][product]={product}&context[device][environment]=bundled&context[device][layout]=desktop&context[device][platform]={platform}&context[device][device]={device}&clientID={clientId}&forwardUrl={redirectUrl}&code={result1.Code}";

       Assert.IsNotNull(result1);
 }
jensenkd commented 4 years ago

once you've redirected the user back to your app, you would call GetAuthTokenFromOAuthPin using the pin returned:


        [TestMethod]
        public void Test_GetAccessTokenFromOAuthPin()
        {
            var plexApi = ServiceProvider.GetService<IPlexClient>();
            string pin = "XXX";
            var result1 = plexApi.GetAuthTokenFromOAuthPin(pin).Result;
            var token = result1.AuthToken;
            Assert.IsNotNull(result1);
        }
jensenkd commented 4 years ago

you'll want to update to Plex.Api 1.0.81

bbakermmc commented 4 years ago

You honestly just need the oauth method to return the URL, and have it take in a fwd address, everything else in the URL you can use the API values, or if you want pass them in as optional overrides

jensenkd commented 4 years ago

v1.0.83


   public void Test_CreateOAuthPin()
   {
       string redirectUrl = "http://test.com";
       var plexApi = ServiceProvider.GetService<IPlexClient>();
       var result1 = plexApi.CreateOAuthPin(redirectUrl).Result;

       var url = result1.Url;

       Assert.IsNotNull(result1);
   }
jensenkd commented 4 years ago

Also renaming the option names to match Plex in next version:


    public class ClientOptions
    {
        public string Product { get; set; } = "Unknown";
        public string DeviceName { get; set; } = "Unknown";
        public Guid ClientId { get; set; } = Guid.NewGuid();
        public string Version { get; set; } = "v1";
        public string Platform { get; set; } = "Web";
    }
bbakermmc commented 4 years ago

For some reason the the URL yours generates looks almost identical except for the "code" which would be correct, but doesnt auth lol, use previous way to get url, fine.

image

bbakermmc commented 4 years ago

In the other code the "ClientId" is a string not a guid, thus my clientid is just "PlexApps"

jensenkd commented 4 years ago

Changed ClientId to string in 1.0.87

bbakermmc commented 4 years ago

@jensenkd Doesnt look like .87 is pushed out on nuget.

jensenkd commented 4 years ago

@bbakermmc should be there now.

bbakermmc commented 4 years ago

@jensenkd Thanks. .86 was super messed up. Changing the ClientId to a string worked for me. The oath stuff is working for me now.

Incase people want to know how I did it:

public class PlexLoginController : Controller
    {
        private readonly HttpClient _httpClient = new HttpClient();
        private readonly PlexOAuthClient _plexOAuthClient;
        private readonly IPlexClient _plexClient;

        public PlexLoginController(PlexOAuthClient plexOAuthClient, IPlexClient plexClient)
        {
            _plexOAuthClient = plexOAuthClient;
            _plexClient = plexClient;
        }

        public async Task<IActionResult> IndexAsync()
        {
            var redirectUrl = Url.Action("PlexReturn", "PlexLogin", null, Request.Scheme);
            //var oauthUrl = await _plexOAuthClient.GetOAuthUrl(redirectUrl);
            var oauthUrl = await _plexClient.CreateOAuthPin(redirectUrl);
            HttpContext.Session.SetString("PlexOauthId", oauthUrl.Id.ToString());
            return Redirect(oauthUrl.Url);
        }

        public async Task<IActionResult> PlexReturn()
        {
            var oauthId = HttpContext.Session.GetString("PlexOauthId");
            var oAuthPin = await _plexClient.GetAuthTokenFromOAuthPin(oauthId);

            if (string.IsNullOrEmpty(oAuthPin.AuthToken))
                throw new Exception("Plex auth failed.");
            HttpContext.Session.Remove("PlexOauthId");
            HttpContext.Session.SetString("PlexKey", oAuthPin.AuthToken);

            return Redirect("/plex");
        }
    }
jensenkd commented 4 years ago

Added example to repo README. Thx @bbakermmc