ASP.Net MVC 5 Google Authentication with Scope - asp.net-mvc

I'm trying to get ASP.Net MVC 5 Google OAuth2 authentication working correctly.
When I set pass in a GoogleOauth2AuthenticationOptions without any scope, then I'm able to log in successfully.
var googlePlusOptions = new GoogleOAuth2AuthenticationOptions
{
ClientId = googleClientId,
ClientSecret = googleClientSecret,
SignInAsAuthenticationType = DefaultAuthenticationTypes.ExternalCookie,
Provider = new GoogleOAuth2AuthenticationProvider()
{
OnAuthenticated = async ctx =>
{
ctx.Identity.AddClaim(new Claim("urn:tokens:googleplus:accesstoken", ctx.AccessToken));
}
},
};
app.UseGoogleAuthentication(googlePlusOptions);
Then this call will return an ExternalLoginInfo object with all the properties set
ExternalLoginInfo loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
When I add any scope though, then I don't get any login info returned. It's just null.
var googlePlusOptions = new GoogleOAuth2AuthenticationOptions
{
ClientId = googleClientId,
ClientSecret = googleClientSecret,
SignInAsAuthenticationType = DefaultAuthenticationTypes.ExternalCookie,
Provider = new GoogleOAuth2AuthenticationProvider()
{
OnAuthenticated = async ctx =>
{
ctx.Identity.AddClaim(new Claim("urn:tokens:googleplus:accesstoken", ctx.AccessToken));
}
},
};
googlePlusOptions.Scope.Add(YouTubeService.Scope.Youtube);
app.UseGoogleAuthentication(googlePlusOptions);
Then the call to get external info just returns null.
ExternalLoginInfo loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
In the Google dev console, I have the following APIs turned on..
Analytics API
BigQuery API
Google Cloud SQL
Google Cloud Storage
Google Cloud Storage JSON API
Google+ API
Google+ Domains API
Identity Toolkit API
YouTube Analytics API
YouTube Data API v3
Something about adding scope to the options is breaking GetExternalLoginInfoAsync.

If anyone's still having trouble with this with the latest Microsoft
OWIN middleware (3.0.0+)...
I noticed from Fiddler that by default, the following scope is sent to accounts.google.com:
scope=openid%20profile%20email
If you add your own scope(s) via GoogleOAuth2AuthenticationOptions.Scope.Add(...), then the scope becomes:
scope=YOUR_SCOPES_ONLY
Therefore, you need to add the default scopes too (or at least, this fixed the issue for me):
var googlePlusOptions = new GoogleOAuth2AuthenticationOptions {
...
};
// default scopes
googlePlusOptions.Scope.Add("openid");
googlePlusOptions.Scope.Add("profile");
googlePlusOptions.Scope.Add("email");
// additional scope(s)
googlePlusOptions.Scope.Add("https://www.googleapis.com/auth/youtube.readonly");

So, I figured this out, with a lot of help from http://www.beabigrockstar.com/blog/google-oauth-sign-asp-net-identity. It turns out that the built in Google authentication provider for MVC is openId only. That's why adding a scope broke it. Using Fiddler, I was able to see the GET request to accounts.google.com, which included "scope=openid" in the querystring.
By switching to the GooglePlusOAuth2 provider in the link above, or on Nuget https://www.nuget.org/packages/Owin.Security.GooglePlus and using the provider name of "GooglePlus", I was able to succesfully add the scopes and still get back the login info from GetExternalLoginInfoAsync.

The changes Google has made to their auth mechanisms have been reflected in version 3.0.0 of Microsoft Owin middleware. As you have identified correctly, one of the changes have been moving the OAuth endpoint to Google+ (https://www.googleapis.com/plus/v1/people/me).
So, the key is to:
upgrade the OWIN middleware to version 3.0.0
enable Google+ API for your app in Google Developers Console

Related

How I can call an api from MVC .net 4.7.2 using Microsoft Identity Planform (Azure AD

I am following a tutorial from microsoft docs and I have created an api with Microsoft Identity Platform using Azure AD in asp.net core 5.
The tutorialI followed shows how to call an api from asp.net core 5, and I have done that part but now I want to call the api from asp.net 4.7.2. Since I am new to apis and example I am finding are not using Microsoft Identity platform to call an api secured by microsoft identity
Can someone point me to document, tutorial, or code which shows me how I can call the api. Code should be written in asp.net not core.
I have done some part but stuck on calling the api.
See the below code
Api methods:
I have already setup the api and web app in Azure portal and configured permission to 2 of the scope.
Method in api.
GetCategory()
GetCatalog()
private async Task OnAuthorizationCodeReceivedAsync(AuthorizationCodeReceivedNotification
notification)
{
notification.HandleCodeRedemption();
var idClient = ConfidentialClientApplicationBuilder.Create(clientId)
.WithRedirectUri(redirectUri)
.WithClientSecret(clientSecret)
.WithAuthority(authority)
.Build();
var signedInUser = new ClaimsPrincipal(notification.AuthenticationTicket.Identity);
try
{
var apiScope = "catalog.Read, Category.Read";
string[] scopes = apiScope.Split(' ');
var result = await idClient.AcquireTokenByAuthorizationCode(
scopes, notification.Code).ExecuteAsync();
//rest of the code to call the api for both scope
// and if i have to do add some code to controller
Not sure if you are still looking for an answer but here it goes.
Once you get the accessToken with required scope, you just need to add it as Authorization Header when you make a call to the API:
const string Scheme = "Bearer";
var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, url);
httpRequestMessage.Headers.Authorization = new AuthenticationHeaderValue(Scheme, result.AccessToken);
var result = await httpClient.SendAsync(httpRequestMessage)

How to make secure authentication for .NET Core Web API?

I am developing an app with .NET Core Web API, Entity Framework and React. I've been reading a lot recently about possible authentication techniques for my API and I've discovered that plain JWT is not entirely secure, so at first I decided to use OpenID Connect with IdentityServer 4. I understand the idea behind OAuth 2.0 and OpenID Connect is to hide user credentials during login process and to involve external authentication provider in issuing an access token, but I don't want to rely on such services because not everyone have an account on Facebook etc. I consider this as an optional way to login. I want to give users an ability to sign in with just login and password. So what is the best (secure) way to accomplish this in modern web apps?
Having project 1 as Client App, project 2 as API Resources and project 3 as Authorization Service (IdentityServer4), I consider following scenarios:
A user is able to create an account on Authorization Service which is responsible for issuing a token required to get access to API Resources through Client App. Authorization Service is registered as authorization provider only for my Client App.
Get authorization token from Authorization Service using resource owner password grant - this one is not recommended by the specs but in my case since user must provide credentials to Authorization Service anyway and I will be hosting every project I can't see any problem.
Don't bother with OAuth and implement authorization mechanism using ASP.NET Core Identity + bearer token authentication.
Any ideas or recommendations highly apprecieated.
I use the JwtBearer package, wire it up in your Startup.cs Configure method like
.UseJwtBearerAuthentication(new JwtBearerOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
TokenValidationParameters = new TokenValidationParameters
{
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(Configuration["AppSettings:AuthConfig:SecretKey"])),
ValidateIssuer = true,
ValidIssuer = Configuration["AppSettings:AuthConfig:Issuer"],
ValidateAudience = true,
ValidAudience = Configuration["AppSettings:AuthConfig:Audience"],
ValidateLifetime = true,
}
})
and my login action on my User controller looks like
[HttpPost]
public string Post([FromBody]LoginRequest request)
{
var contact = dbContext.Contacts.Where(c => c.Active && c.Email == request.Email).Select(c => new { c.Id, c.PasswordHash }).SingleOrDefault();
if (contact == null || !Security.PasswordHash.ValidatePassword(request.Password, contact.PasswordHash))
{
return string.Empty;
}
var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(appSettings.AuthConfig.SecretKey));
var now = DateTime.UtcNow;
var claims = new Claim[]
{
new Claim(JwtRegisteredClaimNames.Sub, contact.Id.ToString()),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Iat, DateTimeOffset.Now.ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64)
};
var jwt = new JwtSecurityToken(
issuer: appSettings.AuthConfig.Issuer,
audience: appSettings.AuthConfig.Audience,
claims: claims,
notBefore: now,
expires: now.AddDays(30),
signingCredentials: new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256));
jwt.Payload.Add("roles", dbContext.ContactRoles.Where(cr => cr.ContactId == contact.Id).Select(ur => ur.Role.Name).ToArray());
return new JwtSecurityTokenHandler().WriteToken(jwt);
}
I use a JWT package for Angular on the client, there may be something similar for React.

To retrieve access token

I have created a MVC application to escalate work to other person inside my organization. I have added all the members in my organization to AAD,
and registered an application there, created app service and linked that app service to registered app with SSO enabled.
Now every time someone visits the app, they can login successfully using their respective credential.
What I want to do know is to retrieve all the members in my AAD and display them inside dropdown list so that anyone can escalate to others by just looking in the dropdown list.
I have tried with sample graph SDK to get the name of users in my organization
with this code
private string redirectUri = ConfigurationManager.AppSettings["ida:RedirectUri"];
private string appId = ConfigurationManager.AppSettings["ida:AppId"];
private string appSecret = ConfigurationManager.AppSettings["ida:AppSecret"];
private string scopes = ConfigurationManager.AppSettings["ida:GraphScopes"];
public async Task<string> GetUserAccessTokenAsync()
{
string signedInUserID = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value;
HttpContextWrapper httpContext = new HttpContextWrapper(HttpContext.Current);
TokenCache userTokenCache = new SessionTokenCache(signedInUserID, httpContext).GetMsalCacheInstance();
//var cachedItems = tokenCache.ReadItems(appId); // see what's in the cache
ConfidentialClientApplication cca = new ConfidentialClientApplication(
appId,
redirectUri,
new ClientCredential(appSecret),
userTokenCache,
null);
try
{
AuthenticationResult result = await cca.AcquireTokenSilentAsync(scopes.Split(new char[] { ' ' }), cca.Users.First());
return result.AccessToken;
}
// Unable to retrieve the access token silently.
catch (Exception)
{
HttpContext.Current.Request.GetOwinContext().Authentication.Challenge(
new AuthenticationProperties() { RedirectUri = "/" },
OpenIdConnectAuthenticationDefaults.AuthenticationType);
throw new ServiceException(
new Error
{
Code = GraphErrorCode.AuthenticationFailure.ToString(),
Message = Resource.Error_AuthChallengeNeeded,
});
}
}
with some change in scope.
<add key="ida:AppId" value="xxxxx-xxxxxxx-xxxxxx-xxxxxxx"/>
<add key="ida:AppSecret" value="xxxxxxxxxxx"/>
<add key="ida:RedirectUri" value="http://localhost:55065/"/>
<add key="ida:GraphScopes" value="User.ReadBasic.All User.Read Mail.Send Files.ReadWrite"/>
This enables me to get basic details of all user in my organization.
But how I can achieve this in my app where authentication related stuffs are done in azure only, and there is no code for authentication and authorization in entire solution.
Thanks
Subham, NATHCORP, INDIA
But how I can achieve this in my app where authentication related stuffs are done in azure only, and there is no code for authentication and authorization in entire solution.
Based on my understanding, you are using the build-in feature App Service Authentication / Authorization. You could follow here to configure your web app to use AAD login. And you need to configure the required permissions for your AD app as follows:
Note: For Azure AD graph, you need to set the relevant permissions for the Windows Azure Active Directory API. For Microsoft Graph, you need to configure the Microsoft Graph API.
Then, you need to configure additional settings for your web app. You could access https://resources.azure.com/, choose your web app and update App Service Auth Configuration as follows:
Note: For using Microsoft Graph API, you need to set the resource to https://graph.microsoft.com. Details, you could follow here.
For retrieving the access token in your application, you could get it from the request header X-MS-TOKEN-AAD-ACCESS-TOKEN. Details, you could follow Working with user identities in your application.
Moreover, you could use Microsoft.Azure.ActiveDirectory.GraphClient package for Microsoft Azure Active Directory Graph API, Microsoft.Graph package for Microsoft Graph API using the related access token.

MVC5 app using Azure Active Directory + REST API -- to auth for PowerBI / O365

I'm trying to adapt the WebAPI example shown here, to use in MVC5:
https://msdn.microsoft.com/en-US/library/dn931282.aspx#Configure
I have a regular AccountController based login system, but I also need the user to login via OAuth into PowerBI, so I can pull datasets via the PowerBI REST API. However, I'm gettting the ClaimsPrincipal.Current.FindFirst(..) to be null.
private static async Task<string> getAccessToken()
{
// Create auth context (note: token is not cached)
AuthenticationContext authContext = new AuthenticationContext(Settings.AzureADAuthority);
// Create client credential
var clientCredential = new ClientCredential(Settings.ClientId, Settings.Key);
// Get user object id
var userObjectId = ClaimsPrincipal.Current.FindFirst(Settings.ClaimTypeObjectIdentifier).Value;
// Get access token for Power BI
// Call Power BI APIs from Web API on behalf of a user
return authContext.AcquireToken(Settings.PowerBIResourceId, clientCredential, new UserAssertion(userObjectId, UserIdentifierType.UniqueId.ToString())).AccessToken;
}
It all works fine in the sample app (a WebAPI project). I've also configured the OWIN app.UseOpenIdConnectAuthentication stuff in Startup.Auth.cs.
It seems the issue is the only type of Claim I have in 'ClaimsPrincipal.Current' is a 'CookieAuthentication' - it is missing the http://schemas.microsoft.com/identity/claims/objectidentifier Claim.
Also...the Microsoft OAuth window never opens in the browser...however, the error is within the ActiveDirectory related code...that code shouldn't need an OAuth token in the first place, right?
The recommended way to do this is to use the code that the Open ID Connect middleware will automatically retrieve for you. There is relevant sample here:
https://github.com/AzureADSamples/WebApp-WebAPI-OpenIDConnect-DotNet
This sample uses OAuth to get a token for the AAD Graph API. I don't know PowerBI but I believe that this is exactly analogous to getting a token for PowerBI.
Pay attention in particular to this file:
https://github.com/AzureADSamples/WebApp-WebAPI-OpenIDConnect-DotNet/blob/master/TodoListWebApp/App_Start/Startup.Auth.cs
AuthorizationCodeReceived = (context) =>
{
var code = context.Code;
ClientCredential credential = new ClientCredential(clientId, appKey);
string userObjectID = context.AuthenticationTicket.Identity.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
AuthenticationContext authContext = new AuthenticationContext(Authority, new NaiveSessionCache(userObjectID));
AuthenticationResult result = authContext.AcquireTokenByAuthorizationCode(code, new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)), credential, graphResourceId);
return Task.FromResult(0);
},
The code above is called on every successful authentication, and ADAL is used to retrieve a token to the Graph API. At this point the only reason to get a token for the Graph API is to exchange the short lived auth code for a longer lived refresh token and get that stored in the cache. That is why the 'result' is never used.
Later, in the following file, the cache is employed to retrieve the token and use it to access the graph:
https://github.com/AzureADSamples/WebApp-WebAPI-OpenIDConnect-DotNet/blob/master/TodoListWebApp/Controllers/UserProfileController.cs
string tenantId = ClaimsPrincipal.Current.FindFirst(TenantIdClaimType).Value;
string userObjectID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
AuthenticationContext authContext = new AuthenticationContext(Startup.Authority, new NaiveSessionCache(userObjectID));
ClientCredential credential = new ClientCredential(clientId, appKey);
result = authContext.AcquireTokenSilent(graphResourceId, credential, new UserIdentifier(userObjectID, UserIdentifierType.UniqueId));
This time the token is actually used.
Substitute PowerBI for Graph API in the sample and I think you should be good to go.
Note that one other thing to pay attention to is the cache implementation. This file contains an appropriately name NaiveSessionCache.
https://github.com/AzureADSamples/WebApp-WebAPI-OpenIDConnect-DotNet/blob/master/TodoListWebApp/Utils/NaiveSessionCache.cs
If you have multiple front ends you will need to implement your own, less naive, session cache so that all the front ends can share the same cache.
A potential workaround, at least for me, is to use the "native app" setup on Azure AD and follow this workflow, instead of the web app + oauth workflow:
https://msdn.microsoft.com/en-US/library/dn877545.aspx

MVC 5 Gmail API Integration

I'm trying to use the GMail API in an MVC 5 project, but I seem to having difficulties on how to achieve that using the Owin Middleware for Authentication
I'm able to login via a Google account, and I can also get the user token as such
var googleOptions = new GoogleOAuth2AuthenticationOptions
{
ClientId = "xxx",
ClientSecret = "yyy",
SignInAsAuthenticationType = DefaultAuthenticationTypes.ExternalCookie,
Provider = new GoogleOAuth2AuthenticationProvider()
{
OnAuthenticated = async ctx =>
{
ctx.Identity.AddClaim(new Claim("urn:tokens:google:accesstoken", ctx.AccessToken));
}
},
};
app.UseGoogleAuthentication(googleOptions);
I get the access token as I would expect, but Google Quicktart Tutorial found here seems to suggest a very different way to accomplish the Authentication
Is there a way I can use this AccessToken to create the objects required in the tutorial ?
Or are these two completely different?

Resources