Social Login authentication in an API - asp.net-core

I am using Microsoft Owin/Katana and implemented the social login authentication in a Web Forms and MVC application which is working perfectly.
I have a requirement to convert the implementation into an API (controllers). In my MVC application, I am calling this line of code inside the login controller,
HttpContext.GetOwinContext().Authentication.Challenge(properties, AuthenticationProvider);
The user is redirected to the login screen of the provider (i.e. Google, Facebook, Twitter).
I was hoping to achieve the following,
User call /api/login controller of my API and it checks the provider and then using the access key and secret key, it returns the login URL as string or JSON and the consumer of the API will redirect to that URl. This way the secret and access keys are not exposed and just the URL is returned.
The problem is that there is no way I am able to find to get the login request URL of the provider. The Challenge method is void and it just redirects the user which in case of an API is not acceptable.
I have used the following code in a Web API,
var properties = new AuthenticationProperties() { RedirectUri = this.RedirectUri };
if (UserId != null)
{
properties.Dictionary[XsrfKey] = UserId;
}
MessageRequest.GetOwinContext().Authentication.Challenge(properties, AuthenticationProvider);
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
response.RequestMessage = MessageRequest;
return Task.FromResult(response);
When I call the API using Postman then it returns the whole HTML of the login page. When I call the API URL directly in the browser then it redirects to the login page correctly but I just want to return the Url of the provider and the consumer should redirect itself.
Is there any way to achieve this?

Related

Token based authentication for both Web App and Web API using Azure AD B2C

Scenario:
Both Web application and Web API need to be authenticated and protected from the server side.
Requirement:
Web application is serving the contents for the browser and browser should be calling Web API directly (i.e. Browser to API).
Question:
Is it possible to authenticate both Web APP and the API using tokens?
Any sample code or clear direction would be highly appreciated.
Normally web applications are authenticated using cookies and APIs are authenticated using tokens.There are some sample projects available here but they are either browser to API (SPA token based) or Server side Web App calling API from server to server.
UPDATE 1
App is saving the TokenValidationParameters and used bootstrapContext.Token within the app controller to grab for server to server communication.
As per #dstrockis, I'm trying to grab the id_token from the Web App soon after the end of validation (not within the app contrller).
I'm using SecurityTokenValidated invoker in OpenIdConnectAuthenticationOptions.Notifications within the Startup class. SecurityTokenValidated receives a parameter of type SecurityTokenValidatedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> but I'm not sure where to find the id_token within it. Method is below.
private OpenIdConnectAuthenticationOptions CreateOptionsFromPolicy(string policy)
{
return new OpenIdConnectAuthenticationOptions
{
// For each policy, give OWIN the policy-specific metadata address, and
// set the authentication type to the id of the policy
MetadataAddress = String.Format(aadInstance, tenant, policy),
AuthenticationType = policy,
// These are standard OpenID Connect parameters, with values pulled from web.config
ClientId = clientId,
RedirectUri = redirectUri,
PostLogoutRedirectUri = redirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = OnAuthenticationFailed,
//NEW METHOD INVOKE ************************************
//******************************************************
SecurityTokenValidated = OnSecurityTokenValidated
//******************************************************
},
Scope = "openid",
ResponseType = "id_token",
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
SaveSigninToken = true
},
};
}
//NEW METHOD ************************************
private Task OnSecurityTokenValidated(
SecurityTokenValidatedNotification<OpenIdConnectMessage,
OpenIdConnectAuthenticationOptions> arg)
{
//QUESTION ********************************************************
//How to find the just saved id_token using incoming parameter, arg
//*****************************************************************
return Task.FromResult(0);
}
UPDATE 2
Instead of SecurityTokenValidated, I tried AuthorizationCodeReceived and it's not getting called at all. As discussed here, my redirect url does have an ending slash as well.
Any Ideas?
Our ASP.NET OpenID Connect middleware which supports AAD B2C is built to rely on cookie authentication from a browser. It doesn't accept tokens in a header or anything like that for securing web pages. So I'd say if you want to serve HTML from your web app in the classic way, you need to use cookies to authenticate requests to the web app.
You can definitely get & store tokens within the browser and use those to access your web API, even if you use cookies to authenticate to the web app. There's two patterns I'd recommend:
Perform the initial login using the OpenID Connect Middleware, initiating the flow from the server side as described in the samples. Once the flow completes, the middleware will validate the resulting id_token and drop cookies in the browser for future requests. You can instruct the middleware to save the id_token for later use by using the line of code written here. You can then somehow pass that id_token down to your browser, cache it, and use it to make requests to the API.
The other pattern is the inverse. Start by initiating the login from javascript, using the single page app pattern from the B2C documentation. Cache the resulting id_tokens in the browser, and use them to make API calls. But when the login completes, you can send a request to your web app with the id_token in the body, triggering the OpenID Connect middleware to process the request and issue a session cookie. If you want to know the format of that request, I'd recommend inspecting a regular server side OpenID Connect flow.
Found the answer to my own question and adding here for the future reference.
After a successful validation, id_token can be accessed by invoking the SecurityTokenValidated notification. Code sample is below.
private Task OnSecurityTokenValidated(
SecurityTokenValidatedNotification<OpenIdConnectMessage,
OpenIdConnectAuthenticationOptions> arg)
{
//Id Token can be retrieved as below.
//**************************************
var token = arg.ProtocolMessage.IdToken;
return Task.FromResult(0);
}
However, saving this directly into a browser cookie may not be secure.

Redirect unauthorized requests to Azure AD for login

I've got a WebAPI instance running in Azure that is secured with Azure AD. A mobile app connects to this API using bearer tokens, works great. When I try calling the API from the browser, though, it returns a 401 because I'm not logged in. That's true because I'm not presented with a login screen.
My API doesn't have any UI so what I'd want it to do is to forward the user to Azure AD login and return to the API endpoint they were calling after authentication.
If I go to the Azure portal, there's a setting that says "Action to take when the request is not authorized". If I set that one to "Log in with Azure Active Directory", it behaves the way I want it to. But... I have some endpoints which need to be accessed anonymously, and this setting catches all requests, not caring about any [AllowAnonymous] attributes.
So any request to an endpoint labeled Authorize that is not authorized yet should be forwarded to Azure AD login, all others should be allowed.
Add a DelegatingHandler to your web api project and register it in WebApiConfig.cs:
config.MessageHandlers.Add(new UnAuthorizedDelegatehandler());
public class UnAuthorizedDelegatehandler: DelegatingHandler
There you can check for 401 status codes and do the redirect to whatever and also apply a redirect url as querystring parameter.
HttpResponseMessage rm = await base.SendAsync(request, cancellationToken);
if (rm.StatusCode == HttpStatusCode.Unauthorized)
{
// configure the redirect here
return rm;
}

Get Access token with Azure AD multi-tenant openID authentication

I followed the sample code here to create a MVC web app with Azure AD multi-tenant OpenID authentication. I use the following code to get user sign in.
public void SignIn()
{
if (!Request.IsAuthenticated)
{
HttpContext.GetOwinContext().Authentication.Challenge(new AuthenticationProperties { RedirectUri = "/" }, OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
}
Now I need to send a web api call which is protected by my Azure AD as well. Currently, before I send the request I use ADAL library to ask users to login again and get the access token like this.
AuthenticationContext ac = new AuthenticationContext(authority);
AuthenticationResult ar = ac.AcquireToken(resourceID, clientID, redirectURI, PromptBehavior.Always);
string accessToken = ar.AccessToken;
However, since the authentication used in the MVC(if the user is from my AD) is the same as the one used to protect the web api. I'm wondering if there is a way to get the access token when user login with this openID authentication so that I can skip the second login with ADAL?
UPDATE:
Following vibronet's answer, I am trying to use the following code to get the token:
string authority = "https://login.windows.net/ucdavisprojecthotmail.onmicrosoft.com";
ClientCredential clientcred = new ClientCredential(clientId, appKey);
AuthenticationContext authContext = new AuthenticationContext(authority);
AuthenticationResult result = authContext.AcquireTokenSilent(resourceID, clientcred, new UserIdentifier(userObjectID, UserIdentifierType.UniqueId));
Here, this code is used in an MVC web app and the clienId and appKey is the clientID and key of the web API I want to call. The resoureID is the APP ID URI of the web API obtained in Azure portal.
However, I got this error: Failed to acquire token silently. Call method AcquireToken. Anything I was missing?
Absolutely. Check out https://github.com/AzureADSamples/WebApp-WebAPI-MultiTenant-OpenIdConnect-DotNet, it's like the sample you've been working with but with in addition the access token acquisition and use you are asking about. Also note, AcquireTokenSilent can only work if you have a token in the cache - to be used directly or refreshed. FInally: when you ask for a token, you must specify both the ID fo the resource you want a token for, and the clientID of the application doing the request. In your code, you appear to have used the clientID of the target app. Please refer to the sample I linked above, it shows the exact pattern to be used in this scenario.

How to create identity in MVC app with JWT

I'll try to be explicit.
I have a Front End app (MVC), it can communicate with a Facade Web Api (Resource) and get token from an authentication server.
My problem is I can't create an identity in MVC app. This is mu Startup class.
public partial class Startup
{
public void ConfigureAuth(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
});
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions
{
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));
app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
}
When I try to go to a method controller decorated with [Authorize], I get 401 error instead to be redirected to Login page.
Please, I would appreciate any help, advice or example.
Regards,
Typically, unless your app is doing postback's you do not need to enable the cookie authentication with login path. That is for the oauth password login flow (grant_type) where you are internally authorizing your users against your identity database. If you're redirecting to an external authorization api (like facebook) then you don't need to set a login path in your application since the first authorization endpoint that gets hit is your external callback (after you send them to facebook, they will send them back to your external endpoint). The redirect you are getting is because cookie authentication is registered as active authentication mode so it redirects 401's to the login path you set (overriding other OWIN middleware).
If you want to house your authorization server in house, have a look at the link below, it will at the least get you setup with JWT support -
http://bitoftech.net/2014/10/27/json-web-token-asp-net-web-api-2-jwt-owin-authorization-server/

loopback protected routes/ensure login

How do I ensure that a user is logged in before I render a view using loopback?
I can loggin in the front end using my angular app. But I wanted to block anonymous users from viewing the page.
I thought it would be a header, something like headers.authorization_token, but it does not seem to be there.
I am looking for something like connect-ensurelogin for passport, without having to use passport.
This is the $interceptor that solves your problem.
This code detects 401 responses (user not logged in or the access token is expired) from Loopback REST server and redirect the user to the login page:
// Inside app config block
$httpProvider.interceptors.push(function($q, $location) {
return {
responseError: function(rejection) {
if (rejection.status == 401) {
$location.nextAfterLogin = $location.path();
$location.path('/login');
}
return $q.reject(rejection);
}
};
});
And this code will redirect to the requested page once the user is logged in
// In the Login controller
User.login($scope.credentials, function() {
var next = $location.nextAfterLogin || '/';
$location.nextAfterLogin = null;
$location.path(next);
});
Here is one possible approach that has worked for me (details may vary):
Design each of the Pages in your Single Page Angular App to make at one of your REST API calls when the Angular Route is resolved.
Secure all of your REST API Routes using the AccessToken/User/Role/ACL scheme that LoopBack provides.
When no valid Access Token is detected on the REST Server side, pass back a 401 Unauthorized Error.
On the Client Side Data Access, when you detect a 401 on your REST Call, redirect to your Logic Route.
For the smoothest User Experience, whenever you redirect to Login, store the Route the User wanted to access globally
(localStore, $RootScope, etc.) and redirect back there when the User
Logs in and gets a valid Access Token.
Here is the LoopBack Access Control sample: https://github.com/strongloop/loopback-example-access-control