ASP.NET WebAPI 2.2 SPA with social login and no MVC dependencies - c#

I have been designing an application which is just a statically served client page designed to use bearer tokens to authenticate with the backing API, however recently I have been trying to add social login options to the back-end but have found it very difficult to find any examples not using MVC dependencies which I would like to avoid if possible.
This question was a great help to get started: ASP.NET Web API social authentication for Web and Mobile
However I have been struggling to get my project to work in the same manor, basically in the question I referenced he has configured a OAuthAuthorizationServerOptions.AuthorizeEndpointPath like this:
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/token"),
Provider = new ApplicationOAuthProvider(PublicClientId),
AuthorizeEndpointPath = new PathString("/api/account/externallogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
//AllowInsecureHttp = false
};
Also in his backing api account controller he has the following action:
[OverrideAuthentication]
[HostAuthentication(DefaultAuthenticationTypes.ExternalCookie)]
[AllowAnonymous]
[Route("ExternalLogin", Name = "ExternalLogin")]
public async Task<IHttpActionResult> GetExternalLogin(string provider, string error = null)
In this example I have not been able to figure out what the first parameter of the RouteAttribute (template) is actually referencing in the project, if anything, could someone maybe explain what it is doing in this context?
Now when running the sample project provided in the question sending a GET request to 'api/Account/ExternalLogin' the request will be handled on the action in his API account controller and I assume it has something to do with OverrideAuthentication but am getting a little out of my depth here and struggling to find strong examples of other usages of this attribute.
However I am fairly certain I have configured my WebAPI project correctly in the way he has described, however when sending a GET request to my OAuthAuthorizationServerOptions.AuthorizeEndpointPath it is not handled on my API account controller but instead by my implementation of OAuthAuthorizationServerProvider which returns a 'invalid_request' error.
Can anyone think of something that I might be overlooking which is causing my API account controller action to be ignored?
I also had a read through this article but it seems to have been written in an older version of WebAPI:
https://thompsonhomero.wordpress.com/2015/01/21/creating-a-clean-web-api-2-project-with-external-authentication-part-2/
Thanks for any help,
Alex.

Without actually seeing your GET requests that are being made, I can only assume that they do not meet expectations by the OAuth provider.
The provider first validates the request being made, THEN it hands control over to the endpoint's controller. Your code is most likely correct, it's just that the request is malformed.
I made a new project and was able to replicate the issue you describe by making a get request to the AuthorizeEndpointPath. Unfortunately, there's not much to go off of as to why, however if you decompile source, or manage to find the source, you can see what's going on here.
Decompiling the calling code of ApplicationOAuthProvider.ValidateClientRedirectUri I get:
await this.Options.Provider.ValidateClientRedirectUri(clientContext);
if (!clientContext.IsValidated)
{
LoggerExtensions.WriteVerbose(this._logger, "Unable to validate client information");
flag = await this.SendErrorRedirectAsync(clientContext, (BaseValidatingContext<OAuthAuthorizationServerOptions>) clientContext);
}
else
{
OAuthValidateAuthorizeRequestContext validatingContext = new OAuthValidateAuthorizeRequestContext(this.Context, this.Options, authorizeRequest, clientContext);
if (string.IsNullOrEmpty(authorizeRequest.ResponseType))
{
LoggerExtensions.WriteVerbose(this._logger, "Authorize endpoint request missing required response_type parameter");
validatingContext.SetError("invalid_request");
}
else if (!authorizeRequest.IsAuthorizationCodeGrantType && !authorizeRequest.IsImplicitGrantType)
{
LoggerExtensions.WriteVerbose(this._logger, "Authorize endpoint request contains unsupported response_type parameter");
validatingContext.SetError("unsupported_response_type");
}
else
await this.Options.Provider.ValidateAuthorizeRequest(validatingContext);
if (!validatingContext.IsValidated)
{
flag = await this.SendErrorRedirectAsync(clientContext, (BaseValidatingContext<OAuthAuthorizationServerOptions>) validatingContext);
}
else
{
this._clientContext = clientContext;
this._authorizeEndpointRequest = authorizeRequest;
OAuthAuthorizeEndpointContext authorizeEndpointContext = new OAuthAuthorizeEndpointContext(this.Context, this.Options, authorizeRequest);
await this.Options.Provider.AuthorizeEndpoint(authorizeEndpointContext);
flag = authorizeEndpointContext.IsRequestCompleted;
}
}
In this code, you can see that if the request has been validated and the request's specified ResponseType is not provided, it set's the context's error to "invalid_request".
I was able to get the request to go through to the ExternalLogin controller method successfully using the following request URI:
http://localhost:18086/api/Account/ExternalLogin?provider=none&client_id=self&redirect_uri=http://localhost:18086/&response_type=token`
P.S. As far as the route attribute on the controller, the "template" field specifies the string that will be used as a template to match incoming request URIs against to determine where the request should be routed.
P.P.S. Actual source code for the decompiled snippet can be found here

Related

Signalr User never shows authenticated

Added the latest SignalR (6.0.3) to my .net 6 API. Everything works there until I try to grab the user identity - it is always unauthorized. I'm really just trying to get a claim from the token, so maybe I'm looking in the wrong place.
The entire setup is localhost API supplying localhost Vue3 SPA.
Authorization works as intended for all API controller actions, following this tutorial.
SignalR Hub communicates as intended with front-end otherwise - receiving and sending.
The SignalR Hub app.MapHub<ChatHub>("/chat"); is the last line in Program.cs before app.Run();
I tried updating the connection from front-end to include accessTokenFactory function, and I can confirm token is correctly supplied here. However, this should not be necessary with cookie authentication.
In my SignalR hub class is a simple method for getting a particular claim: (the code is rough, just trying to get it to work)
public int GetPlayerId()
{
int id = 0;
try
{
var identity = (ClaimsIdentity)Context.User.Identity;
id = Int32.Parse(identity.FindFirst("playerId").ToString());
} catch (Exception ex)
{
return 0;
}
return id;
}
Context.User looks like this regardless of what I do:
I'm not sure where to even begin to debug, as authorization is working as intended across the entirety of the application otherwise. That would seem to point to a SignalR issue, but the few posts I could find about this were mostly severely outdated and made no discernable impact. According to the documentation, this should "just work" with the application's existing authorization.
Any insight on what to check into or additional details to provide is deeply appreciated.
Edit: Additional information
Adding the [Authorize] decorator to my hub class itself does not appear to work. I am able to send and receive regardless. Authorization continues to work as intended elsewhere.
The JwtMiddleware from the affore-linked authentication scheme did not affect the Context object of the SignalR hub.
Instead of just the accountId, I took the validated JWT token and added an identity to the HttpContext User. This is probably not perfect but I hope it help someone in the future:
var jwtToken = jwtUtils.ValidateJwtToken(token);
if (jwtToken != null)
{
int? accountId = int.Parse(jwtToken.Claims.First(x => x.Type == "id").Value);
if (accountId != null)
{
// attach account to context on successful jwt validation
context.Items["Account"] = await dataContext.Accounts.FindAsync(accountId);
context.User.AddIdentity(new ClaimsIdentity(jwtToken.Claims));
}
}

Identityserver4, parameter question. Authorization code flow

I'm implementing a straight out of the box solution using IDserver4(2.3) targeting .netcore 2.2 that communicates with a FHIR client by calling:
Url/api/openid/openapp?launch=12345t6u-34o4-2r2y-0646-gj6g123456t5&iss=myservice&launchOrganization=tilt
with some HL7 simulated scopes etc. The flow is okay all the way to the token endpoint serving access and id tokens using the quickstart on an IIS with certificates and all the bezels.
My problem lies in that the client requires a parameter to be passed to the external client pointing to a file or something on the server where I have some test patient data stored/or served as Json.
Any competent way to pass a parameter with the body or the header for example? And do you do it at the authorization or the authentication, or along with the tokens? Lets call it context. The service shut me down when i reach it. Says this on their side 'TypeError: Parameter "url" must be a string, not undefined'
Thanks in advance.
Got it using:
public class CustomClaimInjection : ICustomTokenRequestValidator
{
private readonly HttpContext _httpContext;
public CustomClaimInjection(IHttpContextAccessor contextAccessor)
{
_httpContext = contextAccessor.HttpContext;
}
public Task ValidateAsync(CustomTokenRequestValidationContext context)
{
var client = context.Result.ValidatedRequest.Client;
//client.Claims.Add(new Claim("sub", sub)); // this will be [client_sub]
context.Result.CustomResponse = new Dictionary<string, object>
{
{"example-launchcontext", "https://url/" }
};
return Task.CompletedTask;
//return Task.FromResult(0);
}
}
I think I understand your problem now, and I think you would like a successful authentication to return additional information about where the patient's file is stored. I would store this in the token as a claim since it can be expressed as a statement about the subject (the user). This can be done in the registered (through dependency injection) implementation of the IProfileService. In the implementation of 'GetProfileDataAsync' you can set the issued claims using the 'ProfileDataRequestContext' parameter's property 'IssuedClaims'. These claims will be used to populate the id token which is what you should be looking to do.

Google Data API Authorization Redirect URI Mismatch

Background
I am wanting to write a small, personal web app in .NET Core 1.1 to interact with YouTube and make some things easier for me to do and I am following the tutorials/samples in Google's YouTube documentation. Sounds simple enough, right? ;)
Authenticating with Google's APIs seems impossible! I have done the following:
Created an account in the Google Developer Console
Created a new project in the Google Developer Console
Created a Web Application OAuth Client ID and added my Web App debug URI to the list of approved redirect URIs
Saved the json file provided after generating the OAuth Client ID to my system
In my application, my debug server url is set (and when my application launches in debug, it's using the url I set which is http://127.0.0.1:60077).
However, when I attempt to authenticate with Google's APIs, I recieve the following error:
That’s an error.
Error: redirect_uri_mismatch
The redirect URI in the request, http://127.0.0.1:63354/authorize/,
does not match the ones authorized for the OAuth client.
Problem
So now, for the problem. The only thing I can find when searching for a solution for this is people that say
just put the redirect URI in your approved redirect URIs
Unfortunately, the issue is that every single time my code attempts to authenticate with Google's APIs, the redirect URI it is using changes (the port changes even though I set a static port in the project's properties). I cannot seem to find a way to get it to use a static port. Any help or information would be awesome!
NOTE: Please don't say things like "why don't you just do it this other way that doesn't answer your question at all".
The code
client_id.json
{
"web": {
"client_id": "[MY_CLIENT_ID]",
"project_id": "[MY_PROJECT_ID]",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://accounts.google.com/o/oauth2/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_secret": "[MY_CLIENT_SECRET]",
"redirect_uris": [
"http://127.0.0.1:60077/authorize/"
]
}
}
Method That Is Attempting to Use API
public async Task<IActionResult> Test()
{
string ClientIdPath = #"C:\Path\To\My\client_id.json";
UserCredential credential;
using (var stream = new FileStream(ClientIdPath, FileMode.Open, FileAccess.Read))
{
credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
new[] { YouTubeService.Scope.YoutubeReadonly },
"user",
CancellationToken.None,
new FileDataStore(this.GetType().ToString())
);
}
var youtubeService = new YouTubeService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = this.GetType().ToString()
});
var channelsListRequest = youtubeService.Channels.List("contentDetails");
channelsListRequest.Mine = true;
// Retrieve the contentDetails part of the channel resource for the authenticated user's channel.
var channelsListResponse = await channelsListRequest.ExecuteAsync();
return Ok(channelsListResponse);
}
Project Properties
The Original Answer works, but it is NOT the best way to do this for an ASP.NET Web Application. See the update below for a better way to handle the flow for an ASP.NET Web Application.
Original Answer
So, I figured this out. The issue is that Google thinks of a web app as a JavaScript based web application and NOT a web app with server side processing. Thus, you CANNOT create a Web Application OAuth Client ID in the Google Developer Console for a server based web application.
The solution is to select the type Other when creating an OAuth Client ID in the Google Developer Console. This will have Google treat it as an installed application and NOT a JavaScript application, thus not requiring a redirect URI to handle the callback.
It's somewhat confusing as Google's documentation for .NET tells you to create a Web App OAuth Client ID.
Feb 16, 2018 Updated Better Answer:
I wanted to provide an update to this answer. Though, what I said above works, this is NOT the best way to implement the OAuth workflow for a ASP.NET solution. There is a better way which actually uses a proper OAuth 2.0 flow. Google's documentation is terrible in regards to this (especially for .NET), so I'll provide a simple implementation example here. The sample is using ASP.NET core, but it's easily adapted to the full .NET framework :)
Note: Google does have a Google.Apis.Auth.MVC package to help simplifiy this OAuth 2.0 flow, but unfortunately it's coupled to a specific MVC implementation and does not work for ASP.NET Core or Web API. So, I wouldn't use it. The example I'll be giving will work for ALL ASP.NET applications. This same code flow can be used for any of the Google APIs you've enabled as it's dependent on the scopes you are requesting.
Also, I am assuming you have your application set up in your Google Developer dashboard. That is to say that you have created an application, enabled the necessary YouTube APIs, created a Web Application Client, and set your allowed redirect urls properly.
The flow will work like this:
The user clicks a button (e.g. Add YouTube)
The View calls a method on the Controller to obtain an Authorization URL
On the controller method, we ask Google to give us an Authorization URL based on our client credentials (the ones created in the Google Developer Dashboard) and provide Google with a Redirect URL for our application (this Redirect URL must be in your list of accepted Redirect URLs for your Google Application)
Google gives us back an Authorization URL
We redirect the user to that Authorization URL
User grants our application access
Google gives our application back a special access code using the Redirect URL we provided Google on the request
We use that access code to get the Oauth tokens for the user
We save the Oauth tokens for the user
You need the following NuGet Packages
Google.Apis
Google.Apis.Auth
Google.Apis.Core
Google.apis.YouTube.v3
The Model
public class ExampleModel
{
public bool UserHasYoutubeToken { get; set; }
}
The Controller
public class ExampleController : Controller
{
// I'm assuming you have some sort of service that can read users from and update users to your database
private IUserService userService;
public ExampleController(IUserService userService)
{
this.userService = userService;
}
public async Task<IActionResult> Index()
{
var userId = // Get your user's ID however you get it
// I'm assuming you have some way of knowing if a user has an access token for YouTube or not
var userHasToken = this.userService.UserHasYoutubeToken(userId);
var model = new ExampleModel { UserHasYoutubeToken = userHasToken }
return View(model);
}
// This is a method we'll use to obtain the authorization code flow
private AuthorizationCodeFlow GetGoogleAuthorizationCodeFlow(params string[] scopes)
{
var clientIdPath = #"C:\Path\To\My\client_id.json";
using (var fileStream = new FileStream(clientIdPath, FileMode.Open, FileAccess.Read))
{
var clientSecrets = GoogleClientSecrets.Load(stream).Secrets;
var initializer = new GoogleAuthorizationCodeFlow.Initializer { ClientSecrets = clientSecrets, Scopes = scopes };
var googleAuthorizationCodeFlow = new GoogleAuthorizationCodeFlow(initializer);
return googleAuthorizationCodeFlow;
}
}
// This is a route that your View will call (we'll call it using JQuery)
[HttpPost]
public async Task<string> GetAuthorizationUrl()
{
// First, we need to build a redirect url that Google will use to redirect back to the application after the user grants access
var protocol = Request.IsHttps ? "https" : "http";
var redirectUrl = $"{protocol}://{Request.Host}/{Url.Action(nameof(this.GetYoutubeAuthenticationToken)).TrimStart('/')}";
// Next, let's define the scopes we'll be accessing. We are requesting YouTubeForceSsl so we can manage a user's YouTube account.
var scopes = new[] { YouTubeService.Scope.YoutubeForceSsl };
// Now, let's grab the AuthorizationCodeFlow that will generate a unique authorization URL to redirect our user to
var googleAuthorizationCodeFlow = this.GetGoogleAuthorizationCodeFlow(scopes);
var codeRequestUrl = googleAuthorizationCodeFlow.CreateAuthorizationCodeRequest(redirectUrl);
codeRequestUrl.ResponseType = "code";
// Build the url
var authorizationUrl = codeRequestUrl.Build();
// Give it back to our caller for the redirect
return authorizationUrl;
}
public async Task<IActionResult> GetYoutubeAuthenticationToken([FromQuery] string code)
{
if(string.IsNullOrEmpty(code))
{
/*
This means the user canceled and did not grant us access. In this case, there will be a query parameter
on the request URL called 'error' that will have the error message. You can handle this case however.
Here, we'll just not do anything, but you should write code to handle this case however your application
needs to.
*/
}
// The userId is the ID of the user as it relates to YOUR application (NOT their Youtube Id).
// This is the User ID that you assigned them whenever they signed up or however you uniquely identify people using your application
var userId = // Get your user's ID however you do (whether it's on a claim or you have it stored in session or somewhere else)
// We need to build the same redirect url again. Google uses this for validaiton I think...? Not sure what it's used for
// at this stage, I just know we need it :)
var protocol = Request.IsHttps ? "https" : "http";
var redirectUrl = $"{protocol}://{Request.Host}/{Url.Action(nameof(this.GetYoutubeAuthenticationToken)).TrimStart('/')}";
// Now, let's ask Youtube for our OAuth token that will let us do awesome things for the user
var scopes = new[] { YouTubeService.Scope.YoutubeForceSsl };
var googleAuthorizationCodeFlow = this.GetYoutubeAuthorizationCodeFlow(scopes);
var token = await googleAuthorizationCodeFlow.ExchangeCodeForTokenAsync(userId, code, redirectUrl, CancellationToken.None);
// Now, you need to store this token in rlation to your user. So, however you save your user data, just make sure you
// save the token for your user. This is the token you'll use to build up the UserCredentials needed to act on behalf
// of the user.
var tokenJson = JsonConvert.SerializeObject(token);
await this.userService.SaveUserToken(userId, tokenJson);
// Now that we've got access to the user's YouTube account, let's get back
// to our application :)
return RedirectToAction(nameof(this.Index));
}
}
The View
#using YourApplication.Controllers
#model YourApplication.Models.ExampleModel
<div>
#if(Model.UserHasYoutubeToken)
{
<p>YAY! We have access to your YouTube account!</p>
}
else
{
<button id="addYoutube">Add YouTube</button>
}
</div>
<script>
$(document).ready(function () {
var addYoutubeUrl = '#Url.Action(nameof(ExampleController.GetAuthorizationUrl))';
// When the user clicks the 'Add YouTube' button, we'll call the server
// to get the Authorization URL Google built for us, then redirect the
// user to it.
$('#addYoutube').click(function () {
$.post(addYoutubeUrl, function (result) {
if (result) {
window.location.href = result;
}
});
});
});
</script>
As referred here, you need to specify a fix port for the ASP.NET development server like How to fix a port number in asp.NET development server and add this url with the fix port to the allowed urls. Also as stated in this thread, when your browser redirects the user to Google's oAuth page, you should be passing as a parameter the redirect URI you want Google's server to return to with the token response.
I noticed that there is easy non-programmatic way around.
If you have typical monotlith application built in typical MS convention(so not compatible with 12factor and typical DDD) there is an option to tell your Proxy WWW server to rewrite all requests from HTTP to HTTPS so even if you have set up Web App on http://localhost:5000 and then added in Google API url like: http://your.domain.net/sigin-google, it will work perfectly and it is not that bas because it is much safer to set up main WWW to rewrite all to HTTPS.
It is not very good practice I guess however it makes sense and does the job.
I've struggled with this issue for hours in a .net Core application. What finally fixed it for me was, in the Google developers console, to create and use a credential for "Desktop app" instead of a "Web application".
Yeah!! Using credentials of desktop app instead of web app worked for me fine. It took me more than 2 days to figure out this problem. The main problem is that google auth library dose not adding or supporting http://localhost:8000 as redirect uri for web app creds but credentials of desktop app fixed that issue. Cause its supporting http://___ connection instead of https: connection for redirect uri

How to make ASP.NET create authenticated session with Owin OpenId Connect library?

After searching through lots of examples online I'm struggling with what seems to be a fairly simple requirement.
I'm trying to extend an existing ASP.NET Application that uses Form Authentication today so that it can use OpenID Connect for authentication as well as some role information coming from the Identity Provider. In particular I'm integrating with an existing hosted Identity Provider that I do not have control over.
I'm using ASP.NET MVC with the Owin components for OpenIdConnect. Namely,
Microsoft.Owin.Security
Microsoft.Owin.Security.Cookies
Microsoft.Owin.Security.OpenIdConnect
I am successfully able to:
In a web browser -- navigate to a controller method that is secured with the [Authorize] attribute
The Owin components properly redirect me to the Identity Provider where I can authenticate and then and I'm redirected back to my app (NOTE: my Identity Provider requires that a redirect_uri be passed in, so I'm currently setting that as part of the OpenIdConnectAuthenticationOptions startup configuration.)
When the redirect back to my app happens, I'm able to see the access_token and the id_token as part of the query string. Additionally, I've been able to use the access_token to call into the user info endpoint and properly derive information about the user using that token.
So far so good! HOWEVER.
What I'm failing to grasp and what most Owin examples I've seen don't seem to explain: what, if any, extra configuration is required to get ASP.NET to actually create an authenticated session in my application based on the redirect from the Identity Provider back to my application.
The general feeling I get from the documentation is that I should NOT have to do extra configuration within the Owin libraries -- that once I've configured the system to use cookie authentication and the OpenId Connect libraries -- that it should just work. However, this doesn't seem to be as easy as it looks. I'm guessing I'm missing something.
Some specific considerations/observations:
Many examples I've found don't require the RedirectUri to be set in the OpenIdConnectAuthenticationOptions, but my Identity Provider requires this parameter to be set each time.
Very few examples that I've found explain whether the controller method that fires as a result of the RedirectUri being hit should be secured with [Authorize] or left anonymous. In my testing, if I mark it as [Authorize] I get into an infinite redirect loop. If I leave it anonymous, I'm able to see the tokens in the request info but the ASP.NET Session is never created. For example, Request.IsAuthenticated is always false.
As a test I've set breakpoints inside several of the OpenIdConnectAuthenticationNotifications() events and currently I'm only seeing my code break into the RedirectToIdentityProvider event, and NONE of the others seem to hit -- which leads me to believe I'm not configuring this right.
Per suggestions I've found, I've set the authentication node this way in the web.config, but it doesn't seem to make a difference if I exclude this node.
<system.web>
<authentication mode="None" />
</system.web>
To summarize:
Do I need to specifically write code to handle the returning redirect from the Identity Provider to manually set up the ASP.NET Session (cookie etc.) for the given user? and
If so, should this code go in the controller method that is called as a result of RedirectUri being hit, or should the code go into one of the "Notifications" events available within OpenIdConnectAuthenticationNotifications()?
Lastly, if I'm NOT supposed to be setting up the Authenticated session manually after redirect from the Identity Provider (if it's supposed to work automatically), any suggestions for common mistakes on this configuration?
For completeness:
My Owin pipeline Startup Configuration method:
public void Configuration(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
//no problems on these as far as I can tell
ClientId = "client_id_string",
ClientSecret = "client_secret_string",
Authority = "url_to_identity_provider",
Scope = "email name etc",
//I'm properly redirected to this URL but not sure
//if I should need to create the session manually
RedirectUri = "http://mymachine/mymvcapp/authorize",
//this causes the redirection to come with the access_token,
//which is valid
ResponseType = "token",
SignInAsAuthenticationType = "Cookies",
Notifications = new OpenIdConnectAuthenticationNotifications()
{
RedirectToIdentityProvider = (context) =>
{
//I'm able to break into this method
return Task.FromResult(0);
},
MessageReceived = (context) =>
{
//doesn't seem to run this line
return Task.FromResult(0);
},
SecurityTokenReceived = (context) =>
{
//doesn't seem to run this line
return Task.FromResult(0);
},
SecurityTokenValidated = (context) =>
{
//doesn't seem to run this line
return Task.FromResult(0);
},
AuthorizationCodeReceived = (context) =>
{
//doesn't seem to run this line
return Task.FromResult(0);
},
AuthenticationFailed = (context) =>
{
//doesn't seem to run this line
return Task.FromResult(0);
},
},
});
}
My secured method that properly initiates the login flow:
[Authorize]
public class HomeController : Controller
{
//I'm sent to the login flow the first time this is hit
public ActionResult Index()
{
return View();
}
}
My method at the RedirectUri that does get called but does indicate that the ASP.NET authenticated session was created:
public class AuthorizeController : Controller
{
// [Authorize] -- currently this Authorize attribute is turned off
//so the method is anonymous.
//If I turn that back on, I get infininte redirect loops to
//the Identity Provider
public ActionResult Index()
{
//the incoming request to this controller method from the
//identity provider DOES include valid access_token and id_token
//(which can be used against the user info endpoint) but does not
//create a valid ASP.NET session for my web app
//Request.IsAuthenticated is always false
//should there be a manual creation of the ASP.NET
//session/cookie information in this controller method?
//note: to me it would make most sense if this attribute was not
//anonymous since it's unlikely that the Request would ever appear
//as IsAuthenticated == true, but if you read the entire question
//it will be clear why I'm trying this method with anonymous access
return View();
}
}
As you found out, you can't put an [Authorize] attribute on the method the external server uses to notify you the user was authorized - the session isn't authorized yet, you're just being notified that it should be.
Fortunately, creating that session is not difficult:
How can I manually create a authentication cookie instead of the default method?
(I'm pretty sure you have to do this yourself with the basic Microsoft Owin stuff - and you always can do it yourself if you want.)

How to supply credentials for a DNN WebAPI endpoint?

I've got a DNN 7 website, with web service endpoints like this:
public class DNNRoleManagementServiceController: DnnApiController
{
//[AllowAnonymous]
[RequireHost]
[HttpGet]
public HttpResponseMessage ManagePortalUserToRole(int PortalID, int UserID, int RoleID, bool RemoveFromRole )
{
//stuff
}
}
It was AllowAnonymous for development, it's now RequireHost because security on your endpoints is good to have. These endpoints should only be used by our system, so [RequireHost] seems to be the right authentication level.
I've got my c# library to call this endpoint:
WebClient webClient = new WebClient();
Uri uri = new Uri(uriString.ToString());
webClient.Credentials = new NetworkCredential("host", "password");
webClient.DownloadStringCompleted += GetStringCompleted;
webClient.DownloadStringAsync(uri);
Even with the right password though, all I get is a 400 or 401 response. So obviously I'm supplying my credentials the wrong way. Only, what's the right way? All the documentation I've found on the DNN WebAPI has been "Apply the [AllowAnonymous] attribute so that anyone and their dog can use your web service!" which is great if the service you're supplying is for public consumption. This isn't one of those cases.
So what's the right method of supplying host credentials so the call will work? I'm really not sure here.
Have you tried doing
[DnnAuthorize(RequireHost=true)]
Don't know if it will work as I've only done it for actual logged in users and not from an external source passing username and password credentials.
You could maybe look at this codeplex project for some more information.
Codeplex: dnndashservice

Categories