Created
February 11, 2022 05:24
-
-
Save MeepsKitten/2aec5b7a4902e7c67f9793132d109204 to your computer and use it in GitHub Desktop.
Twitch Implicit OAuth Unity Example
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Simple Twitch IMPLICIT OAuth flow example | |
* by HELLCAT & MeepsKitten | |
* | |
* At first glance, this looks like more than it actually is. | |
* It's really no rocket science, promised! ;-) | |
* And for any further questions contact me directly or on the Twitch-Developers discord. | |
* | |
* 🐦 https://twitter.com/therealhellcat & https://twitter.com/MeepsKitten | |
* 📺 https://www.twitch.tv/therealhellcat & https://twitch.tv/MeepsKitten | |
*/ | |
using System; | |
using System.IO; | |
using System.Net; | |
using System.Text.RegularExpressions; | |
using UnityEngine; | |
using UnityEngine.Networking; | |
public class TwitchOAuth : MonoBehaviour | |
{ | |
[SerializeField] private string twitchAuthUrl = "https://id.twitch.tv/oauth2/authorize"; | |
[SerializeField] private string twitchClientId = "PUT YOUR CLIENT ID HERE"; | |
[SerializeField] private string twitchRedirectUrl = "http://localhost:8080/"; | |
private string _twitchAuthStateVerify; | |
private string _authToken = null; | |
/// <summary> | |
/// Starts the Twitch OAuth flow by constructing the Twitch auth URL based on the scopes you want/need. | |
/// </summary> | |
public void InitiateTwitchAuth() | |
{ | |
string[] scopes; | |
string s; | |
// list of scopes we want | |
scopes = new[] | |
{ | |
"chat:edit", | |
"chat:read", | |
"channel:read:redemptions", | |
"user:read:subscriptions", | |
"user:read:broadcast", | |
"bits:read", | |
"channel:read:hype_train", | |
"channel:manage:redemptions" | |
}; | |
// generate something for the "state" parameter. | |
// this can be whatever you want it to be, it's gonna be "echoed back" to us as is and should be used to | |
// verify the redirect back from Twitch is valid. | |
_twitchAuthStateVerify = ((Int64)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds).ToString(); | |
// query parameters for the Twitch auth URL | |
s = "client_id=" + twitchClientId + "&" + | |
"redirect_uri=" + UnityWebRequest.EscapeURL(twitchRedirectUrl) + "&" + | |
"state=" + _twitchAuthStateVerify + "&" + | |
"response_type=token&" + | |
"scope=" + String.Join("+", scopes); | |
// start our local webserver to receive the redirect back after Twitch authenticated | |
StartLocalWebserver(); | |
// open the users browser and send them to the Twitch auth URL | |
Application.OpenURL(twitchAuthUrl + "?" + s); | |
} | |
/// <summary> | |
/// Opens a simple "webserver" like thing on localhost:8080 for the auth redirect to land on. | |
/// Based on the C# HttpListener docs: https://docs.microsoft.com/en-us/dotnet/api/system.net.httplistener | |
/// </summary> | |
private void StartLocalWebserver() | |
{ | |
HttpListener httpListener = new HttpListener(); | |
httpListener.Prefixes.Add(twitchRedirectUrl); | |
httpListener.Start(); | |
httpListener.BeginGetContext(new AsyncCallback(IncomingHttpRequest), httpListener); | |
} | |
/// <summary> | |
/// Handles the incoming HTTP request | |
/// </summary> | |
/// <param name="result"></param> | |
private void IncomingHttpRequest(IAsyncResult result) | |
{ | |
HttpListener httpListener; | |
HttpListenerContext httpContext; | |
HttpListenerRequest httpRequest; | |
HttpListenerResponse httpResponse; | |
string responseString; | |
// get back the reference to our http listener | |
httpListener = (HttpListener)result.AsyncState; | |
// fetch the context object | |
httpContext = httpListener.EndGetContext(result); | |
// if we'd like the HTTP listener to accept more incoming requests, we'd just restart the "get context" here: | |
httpListener.BeginGetContext(new AsyncCallback(IncomingAuth),httpListener); | |
// the context object has the request object for us, that holds details about the incoming request | |
httpRequest = httpContext.Request; | |
// build a response to send JS back to the browser for OAUTH Relay | |
httpResponse = httpContext.Response; | |
responseString = "<html><body><b id=\"auth\">Login Complete</b><br>" + | |
"<script type=\"text/javascript\">" + | |
"var xhr = new XMLHttpRequest(); " + | |
$"xhr.open(\"POST\", \"{UnityWebRequest.EscapeURL(twitchRedirectUrl)}\");" + | |
"xhr.send(window.location);" + //Sending the window location (the url bar) from the browser to our listener | |
"</script></body></html>"; | |
byte[] buffer = System.Text.Encoding.UTF8.GetBytes(responseString); | |
// send the output to the client browser | |
httpResponse.ContentLength64 = buffer.Length; | |
System.IO.Stream output = httpResponse.OutputStream; | |
output.Write(buffer, 0, buffer.Length); | |
output.Close(); | |
} | |
private void IncomingAuth(IAsyncResult ar) | |
{ | |
//mostly the same as IncomingHttpRequest | |
HttpListener httpListener; | |
HttpListenerContext httpContext; | |
HttpListenerRequest httpRequest; | |
httpListener = (HttpListener)ar.AsyncState; | |
httpContext = httpListener.EndGetContext(ar); | |
httpListener.BeginGetContext(new AsyncCallback(IncomingAuth), httpListener); | |
httpRequest = httpContext.Request; | |
//this time we take an input stream from the request to recieve the url | |
string url; | |
using (var reader = new StreamReader(httpRequest.InputStream, | |
httpRequest.ContentEncoding)) | |
{ | |
url = reader.ReadToEnd(); | |
} | |
//regex to extract the OAUTH and auth state | |
Regex rx = new Regex(@".+#access_token=(.+)&scope.*state=(\d+)"); | |
var match = rx.Match(url); | |
//if state doesnt match reject data | |
if (match.Groups[2].Value != _twitchAuthStateVerify) return; | |
_authToken = match.Groups[1].Value; | |
Debug.Log("AUTH: " + _authToken); | |
httpListener.Stop(); | |
} | |
/* JS NOTEPAD | |
var xhr = new XMLHttpRequest(); | |
xhr.open(\"POST\", \"http://localhost:8080//\"); | |
xhr.send(window.location);}} | |
*/ | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment