├── OAuth2.cs
├── license.txt
└── readme.md
/OAuth2.cs:
--------------------------------------------------------------------------------
1 | /**
2 | * A simple C# OAuth2 auth-code wrapper library.
3 | *
4 | * @author
5 | * Stian Hanger (pdnagilum@gmail.com)
6 | *
7 | * @source
8 | * https://github.com/nagilum/oauth2-csharp
9 | */
10 |
11 | using System;
12 | using System.Collections.Generic;
13 | using System.IO;
14 | using System.Linq;
15 | using System.Net;
16 | using System.Text;
17 | using System.Web;
18 | using System.Web.Script.Serialization;
19 |
20 | ///
21 | /// A simple C# OAuth2 auth-code wrapper library.
22 | ///
23 | public class OAuth2 {
24 | #region Class functionality
25 |
26 | ///
27 | /// Construct a OAuth2 forwarding URI to redirect with.
28 | ///
29 | /// OAuth2 provider wrapper.
30 | /// URI to redirect back to the system.
31 | /// Language locale for provider interface.
32 | /// URI to redirect system to, for user authorization.
33 | public static string CreateRedirect(OAuth2Provider provider, string redirectUri, string locale = "en") {
34 | var parameters = new Dictionary {
35 | {"client_id", provider.ClientId},
36 | {"display", "page"},
37 | {"locale", locale},
38 | {"redirect_uri", redirectUri},
39 | {"response_type", "code"}
40 | };
41 |
42 | if (provider.Offline)
43 | parameters.Add(
44 | "access_type",
45 | "offline");
46 |
47 | if (!string.IsNullOrWhiteSpace(provider.Scope))
48 | parameters.Add(
49 | "scope",
50 | provider.Scope);
51 |
52 | if (!string.IsNullOrWhiteSpace(provider.State))
53 | parameters.Add(
54 | "state",
55 | provider.State);
56 |
57 | var qs = buildQueryString(parameters);
58 | var url =
59 | provider.AuthUri + "?" +
60 | qs;
61 |
62 | return url;
63 | }
64 |
65 | ///
66 | /// Request a access token by exchanging a auth code.
67 | ///
68 | /// OAuth2 provider wrapper.
69 | /// URI to redirect back to the system.
70 | /// Authorization code.
71 | /// Authentication response object.
72 | public static OAuth2AuthenticateResponse AuthenticateByCode(OAuth2Provider provider, string redirectUri, string code) {
73 | var parameters = new Dictionary {
74 | {"client_id", provider.ClientId},
75 | {"client_secret", provider.ClientSecret},
76 | {"redirect_uri", redirectUri},
77 | {"code", code},
78 | {"grant_type", "authorization_code"}
79 | };
80 |
81 | if (!string.IsNullOrWhiteSpace(provider.Scope))
82 | parameters.Add(
83 | "scope",
84 | provider.Scope);
85 |
86 | if (!string.IsNullOrWhiteSpace(provider.State))
87 | parameters.Add(
88 | "state",
89 | provider.State);
90 |
91 | var reply = request(
92 | provider.AccessTokenUri,
93 | payload: buildQueryString(parameters));
94 |
95 | return interpretReply(reply);
96 | }
97 |
98 | ///
99 | /// Request a new access token by refreshing an old.
100 | ///
101 | /// OAuth2 provider wrapper.
102 | /// Access/refresh token to use.
103 | /// Authentication response object.
104 | public static OAuth2AuthenticateResponse AuthenticateByToken(OAuth2Provider provider, string refreshToken) {
105 | var parameters = new Dictionary {
106 | {"client_id", provider.ClientId},
107 | {"client_secret", provider.ClientSecret},
108 | {"refresh_token", refreshToken},
109 | {"grant_type", "refresh_token"}
110 | };
111 |
112 | if (!string.IsNullOrWhiteSpace(provider.Scope))
113 | parameters.Add(
114 | "scope",
115 | provider.Scope);
116 |
117 | if (!string.IsNullOrWhiteSpace(provider.State))
118 | parameters.Add(
119 | "state",
120 | provider.State);
121 |
122 | var reply = request(
123 | provider.AccessTokenUri,
124 | payload: buildQueryString(parameters));
125 |
126 | return interpretReply(reply);
127 | }
128 |
129 | ///
130 | /// Get user info from the providers user endpoint.
131 | ///
132 | /// OAuth2 provider wrapper.
133 | /// Access token to use.
134 | /// Raw data from the provider.
135 | public static string GetUserInfo(OAuth2Provider provider, string accessToken) {
136 | var parameters = new Dictionary {
137 | {"access_token", accessToken}
138 | };
139 |
140 | return request(
141 | provider.UserInfoUri,
142 | "GET",
143 | buildQueryString(parameters));
144 | }
145 |
146 | #endregion
147 | #region Helper functions
148 |
149 | ///
150 | /// Construct a query-string from dictionary.
151 | ///
152 | /// Set of parameters in dictionary form to construct from.
153 | /// Query-string.
154 | private static string buildQueryString(Dictionary parameters) {
155 | return parameters.Aggregate(
156 | "",
157 | (c, p) => c + ("&" + p.Key + "=" + HttpUtility.UrlEncode(p.Value)))
158 | .Substring(1);
159 | }
160 |
161 | ///
162 | /// Interpret the reply from the auth-call.
163 | ///
164 | /// The string body from the web-request.
165 | /// Authentication response object.
166 | private static OAuth2AuthenticateResponse interpretReply(string reply) {
167 | var response = new OAuth2AuthenticateResponse();
168 |
169 | if (reply.StartsWith("{")) { // JSON
170 | var dict = new JavaScriptSerializer().Deserialize>(reply);
171 |
172 | if (dict.ContainsKey("access_token")) response.AccessToken = dict["access_token"];
173 | if (dict.ContainsKey("refresh_token")) response.RefreshToken = dict["refresh_token"];
174 | if (dict.ContainsKey("state")) response.State = dict["state"];
175 |
176 | var seconds = 0;
177 |
178 | if (dict.ContainsKey("expires")) int.TryParse(dict["expires"], out seconds);
179 | if (dict.ContainsKey("expires_in")) int.TryParse(dict["expires_in"], out seconds);
180 |
181 | if (seconds > 0)
182 | response.Expires = DateTime.Now.AddSeconds(seconds);
183 | }
184 | else if (reply.Contains('&')) { // QueryString
185 | var dict = reply.Split('&');
186 |
187 | foreach (var entry in dict) {
188 | var index = entry.IndexOf('=');
189 |
190 | if (index == -1)
191 | continue;
192 |
193 | var key = entry.Substring(0, index);
194 | var value = entry.Substring(index + 1);
195 |
196 | switch (key) {
197 | case "access_token":
198 | response.AccessToken = value;
199 | break;
200 |
201 | case "refresh_token":
202 | response.RefreshToken = value;
203 | break;
204 |
205 | case "state":
206 | response.State = value;
207 | break;
208 |
209 | case "expires":
210 | case "expires_in":
211 | int seconds;
212 |
213 | if (int.TryParse(value, out seconds))
214 | response.Expires = DateTime.Now.AddSeconds(seconds);
215 |
216 | break;
217 | }
218 | }
219 | }
220 |
221 | return response;
222 | }
223 |
224 | ///
225 | /// Make a web-request and return response string.
226 | ///
227 | /// URI to contact.
228 | /// HTTP method to use.
229 | /// Payload to deliver via query-string or body.
230 | /// Response from web-request.
231 | private static string request(string uri, string method = "POST", string payload = null) {
232 | if (method == "GET" &&
233 | !string.IsNullOrWhiteSpace(payload))
234 | uri += "?" + payload;
235 |
236 | var request = WebRequest.Create(uri) as HttpWebRequest;
237 |
238 | if (request == null)
239 | throw new WebException("Could not create WebRequest.");
240 |
241 | request.Method = method;
242 | request.ContentType = "application/x-www-form-urlencoded;";
243 | request.Expect = null;
244 |
245 | Stream stream;
246 |
247 | if (method == "POST" &&
248 | !string.IsNullOrWhiteSpace(payload)) {
249 | var buffer = Encoding.UTF8.GetBytes(payload);
250 |
251 | request.ContentLength = buffer.Length;
252 | stream = request.GetRequestStream();
253 |
254 | stream.Write(buffer, 0, buffer.Length);
255 | stream.Close();
256 | }
257 | else {
258 | request.ContentLength = 0;
259 | }
260 |
261 | var response = request.GetResponse() as HttpWebResponse;
262 |
263 | if (response == null)
264 | throw new WebException("Could not get response from request.");
265 |
266 | stream = response.GetResponseStream();
267 |
268 | if (stream == null)
269 | throw new WebException("Could not get stream from response.");
270 |
271 | var reader = new StreamReader(stream);
272 | var body = reader.ReadToEnd();
273 |
274 | reader.Close();
275 | stream.Close();
276 |
277 | return body;
278 | }
279 |
280 | #endregion
281 | }
282 |
283 | ///
284 | /// OAuth2 provider wrapper.
285 | ///
286 | public class OAuth2Provider {
287 | public string ClientId { get; set; }
288 | public string ClientSecret { get; set; }
289 | public string AuthUri { get; set; }
290 | public string AccessTokenUri { get; set; }
291 | public string UserInfoUri { get; set; }
292 | public string Scope { get; set; }
293 | public string State { get; set; }
294 | public bool Offline = false;
295 | }
296 |
297 | ///
298 | /// Authentication response object.
299 | ///
300 | public class OAuth2AuthenticateResponse {
301 | public string AccessToken { get; set; }
302 | public string RefreshToken { get; set; }
303 | public DateTime Expires { get; set; }
304 | public string State { get; set; }
305 | }
--------------------------------------------------------------------------------
/license.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2016 Stian Hanger
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | A simple C# OAuth2 auth-code wrapper library.
2 |
3 | ## How to Create the Redirect
4 |
5 | ```csharp
6 | var systemRedirectUri = OAuth2.CreateRedirect(
7 | new OAuth2.OAuth2Provider {
8 | ClientId = "my-client-id",
9 | ClientSecret = "my-client-secret",
10 | Scope = "email",
11 | AuthUri = "https://graph.facebook.com/oauth/authorize",
12 | AccessTokenUri = "https://graph.facebook.com/oauth/access_token",
13 | UserInfoUri = "https://graph.facebook.com/me"
14 | },
15 | "https://awesome-domain.com/my-awesome-oauth-callback");
16 | ```
17 |
18 | This will give you a URI you can just forward the user too. They will sign in and give the app access, which will redirect back to: `https://awesome-domain.com/my-awesome-oauth-callback?code=XXX`
19 |
20 | ## How to Authenticate by Code
21 |
22 | ```csharp
23 | var response = OAuth2.AuthenticateByCode(
24 | new OAuth2.OAuth2Provider { ... },
25 | "same redirect uri as before",
26 | "code from callback from provider");
27 | ```
28 |
29 | The library will make a web request to the provider with the auth code given and, hopefully, return with a valid access token. You are now authorized with the given provider.
--------------------------------------------------------------------------------