diff --git a/OwinOAuthProviders.sln b/OwinOAuthProviders.sln
index 1d462ce..81b5545 100644
--- a/OwinOAuthProviders.sln
+++ b/OwinOAuthProviders.sln
@@ -1,6 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 14
-VisualStudioVersion = 14.0.25420.1
+# Visual Studio 15
+VisualStudioVersion = 15.0.26730.16
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.Security.Providers.ArcGISOnline", "src\Owin.Security.Providers.ArcGISOnline\Owin.Security.Providers.ArcGISOnline.csproj", "{8A49FAEF-D365-4D25-942C-1CAD03845A5E}"
EndProject
@@ -108,6 +108,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.Security.Providers.Eve
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.Security.Providers.WSO2", "src\Owin.Security.Providers.WSO2\Owin.Security.Providers.WSO2.csproj", "{8FD3A9CB-E684-42C0-A8BF-7746FDD3D43C}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.Security.Providers.ArcGISPortal", "src\Owin.Security.Providers.ArcGISPortal\Owin.Security.Providers.ArcGISPortal.csproj", "{18547CA4-D7D3-43C2-81C2-A21FC8151A93}"
+EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Owin.Security.Providers.Podbean", "src\Owin.Security.Providers.Podbean\Owin.Security.Providers.Podbean.csproj", "{A7B95FD4-08AD-499F-B574-07560CC2A63F}"
EndProject
Global
@@ -337,6 +339,7 @@ Global
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
+
SolutionGuid = {EDFDB942-6583-4AD9-A868-B354B5BF07FC}
EndGlobalSection
EndGlobal
diff --git a/OwinOAuthProvidersDemo/App_Start/Startup.Auth.cs b/OwinOAuthProvidersDemo/App_Start/Startup.Auth.cs
index b1bf7d8..2879b4a 100755
--- a/OwinOAuthProvidersDemo/App_Start/Startup.Auth.cs
+++ b/OwinOAuthProvidersDemo/App_Start/Startup.Auth.cs
@@ -4,6 +4,7 @@ using Microsoft.Owin.Security.Cookies;
using Owin;
using Owin.Security.Providers.Evernote;
using Owin.Security.Providers.PayPal;
+using Owin.Security.Providers.ArcGISPortal;
namespace OwinOAuthProvidersDemo
{
@@ -155,6 +156,12 @@ namespace OwinOAuthProvidersDemo
// clientId: "",
// clientSecret: "");
+ //app.UseArcGISPortalAuthentication(new ArcGISPortalAuthenticationOptions(
+ // "My ArcGIS Portal",
+ // "https://arcgisportal.mydomain.com/",
+ // "",
+ // ""));
+
//app.UseWordPressAuthentication(
// clientId: "",
// clientSecret: "");
diff --git a/OwinOAuthProvidersDemo/OwinOAuthProvidersDemo.csproj b/OwinOAuthProvidersDemo/OwinOAuthProvidersDemo.csproj
index 723cc9d..1211eb5 100644
--- a/OwinOAuthProvidersDemo/OwinOAuthProvidersDemo.csproj
+++ b/OwinOAuthProvidersDemo/OwinOAuthProvidersDemo.csproj
@@ -259,6 +259,10 @@
{4fd7b873-1994-4990-aa40-c37060121494}
Owin.Security.Providers.OpenIDBase
+
+ {18547ca4-d7d3-43c2-81c2-a21fc8151a93}
+ Owin.Security.Providers.ArcGISPortal
+
{8a49faef-d365-4d25-942c-1cad03845a5e}
Owin.Security.Providers.ArcGISOnline
diff --git a/README.md b/README.md
index 9bcbf4d..cdb7d36 100644
--- a/README.md
+++ b/README.md
@@ -5,6 +5,7 @@
Provides a set of extra authentication providers for OWIN ([Project Katana](http://katanaproject.codeplex.com/)). This project includes providers for:
- OAuth
- ArcGISOnline
+ - ArcGISPortal
- Asana
- Backlog
- Battle.net
diff --git a/src/Owin.Security.Providers.ArcGISPortal/ArcGISPortalAuthenticationExtensions.cs b/src/Owin.Security.Providers.ArcGISPortal/ArcGISPortalAuthenticationExtensions.cs
new file mode 100644
index 0000000..115201f
--- /dev/null
+++ b/src/Owin.Security.Providers.ArcGISPortal/ArcGISPortalAuthenticationExtensions.cs
@@ -0,0 +1,20 @@
+using System;
+
+namespace Owin.Security.Providers.ArcGISPortal
+{
+ public static class ArcGISPortalAuthenticationExtensions
+ {
+ public static IAppBuilder UseArcGISPortalAuthentication(this IAppBuilder app,
+ ArcGISPortalAuthenticationOptions options)
+ {
+ if (app == null)
+ throw new ArgumentNullException(nameof(app));
+ if (options == null)
+ throw new ArgumentNullException(nameof(options));
+
+ app.Use(typeof(ArcGISPortalAuthenticationMiddleware), app, options);
+
+ return app;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Owin.Security.Providers.ArcGISPortal/ArcGISPortalAuthenticationHandler.cs b/src/Owin.Security.Providers.ArcGISPortal/ArcGISPortalAuthenticationHandler.cs
new file mode 100644
index 0000000..8a32cbe
--- /dev/null
+++ b/src/Owin.Security.Providers.ArcGISPortal/ArcGISPortalAuthenticationHandler.cs
@@ -0,0 +1,234 @@
+using System;
+using System.Collections.Generic;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Security.Claims;
+using System.Threading.Tasks;
+using Microsoft.Owin.Infrastructure;
+using Microsoft.Owin.Logging;
+using Microsoft.Owin.Security;
+using Microsoft.Owin.Security.Infrastructure;
+using Newtonsoft.Json;
+
+namespace Owin.Security.Providers.ArcGISPortal
+{
+ public class ArcGISPortalAuthenticationHandler : AuthenticationHandler
+ {
+ private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string";
+
+ private readonly ILogger _logger;
+ private readonly HttpClient _httpClient;
+ private readonly string _host;
+
+ public ArcGISPortalAuthenticationHandler(HttpClient httpClient, ILogger logger, string host)
+ {
+ _httpClient = httpClient;
+ _logger = logger;
+ _host = host;
+ }
+
+ protected override async Task AuthenticateCoreAsync()
+ {
+ AuthenticationProperties properties = null;
+
+ try
+ {
+ string code = null;
+ string state = null;
+
+ var query = Request.Query;
+ var values = query.GetValues("code");
+ if (values != null && values.Count == 1)
+ {
+ code = values[0];
+ }
+ values = query.GetValues("state");
+ if (values != null && values.Count == 1)
+ {
+ state = values[0];
+ }
+
+ properties = Options.StateDataFormat.Unprotect(state);
+ if (properties == null)
+ {
+ return null;
+ }
+ // OAuth2 10.12 CSRF
+ if (!ValidateCorrelationId(properties, _logger))
+ {
+ return new AuthenticationTicket(null, properties);
+ }
+
+ var requestPrefix = Request.Scheme + "://" + Request.Host;
+ var redirectUri = requestPrefix + Request.PathBase + Options.CallbackPath;
+
+ // Build up the body for the token request
+ var body = new List>
+ {
+ new KeyValuePair("grant_type", "authorization_code"),
+ new KeyValuePair("code", code),
+ new KeyValuePair("redirect_uri", redirectUri),
+ new KeyValuePair("client_id", Options.ClientId),
+ new KeyValuePair("client_secret", Options.ClientSecret)
+ };
+
+ // Request the token
+ var requestMessage = new HttpRequestMessage(HttpMethod.Post, Options.Endpoints.TokenEndpoint);
+ requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
+ requestMessage.Content = new FormUrlEncodedContent(body);
+ var tokenResponse = await _httpClient.SendAsync(requestMessage);
+ tokenResponse.EnsureSuccessStatusCode();
+ var text = await tokenResponse.Content.ReadAsStringAsync();
+
+ // Deserializes the token response
+ dynamic response = JsonConvert.DeserializeObject(text);
+ var accessToken = (string)response.access_token;
+ var refreshToken = (string)response.refresh_token;
+
+ // Get the ArcGISPortal user
+ var userRequest = new HttpRequestMessage(HttpMethod.Get, Options.Endpoints.UserInfoEndpoint + "?f=json&token=" + Uri.EscapeDataString(accessToken));
+ userRequest.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
+ var userResponse = await _httpClient.SendAsync(userRequest, Request.CallCancelled);
+ userResponse.EnsureSuccessStatusCode();
+ text = await userResponse.Content.ReadAsStringAsync();
+ var user = JsonConvert.DeserializeObject(text);
+
+ var context = new ArcGISPortalAuthenticatedContext(Context, user, accessToken, refreshToken, _host)
+ {
+ Identity = new ClaimsIdentity(
+ Options.AuthenticationType,
+ ClaimsIdentity.DefaultNameClaimType,
+ ClaimsIdentity.DefaultRoleClaimType)
+ };
+ if (!string.IsNullOrEmpty(context.Id))
+ {
+ context.Identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, context.Id, XmlSchemaString, Options.AuthenticationType));
+ }
+ if (!string.IsNullOrEmpty(context.UserName))
+ {
+ context.Identity.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, context.UserName, XmlSchemaString, Options.AuthenticationType));
+ }
+ if (!string.IsNullOrEmpty(context.Email))
+ {
+ context.Identity.AddClaim(new Claim(ClaimTypes.Email, context.Email, XmlSchemaString, Options.AuthenticationType));
+ }
+ if (!string.IsNullOrEmpty(context.Name))
+ {
+ context.Identity.AddClaim(new Claim("urn:ArcGISPortal:name", context.Name, XmlSchemaString, Options.AuthenticationType));
+ }
+ if (!string.IsNullOrEmpty(context.Link))
+ {
+ context.Identity.AddClaim(new Claim("urn:ArcGISPortal:url", context.Link, XmlSchemaString, Options.AuthenticationType));
+ }
+
+ context.Properties = properties;
+
+ await Options.Provider.Authenticated(context);
+
+ return new AuthenticationTicket(context.Identity, context.Properties);
+ }
+ catch (Exception ex)
+ {
+ _logger.WriteError(ex.Message);
+ }
+ return new AuthenticationTicket(null, properties);
+ }
+
+ protected override Task ApplyResponseChallengeAsync()
+ {
+ if (Response.StatusCode != 401)
+ {
+ return Task.FromResult