I am trying to build an MVC WebApp supporting multiple external providers using Google, Facebook, Microsoft and Twitter. So far with help from multiple online articles, I have succeeded in building a simple cookie based authentication scheme along with Google as authentication provider. Somehow the same logic for adding Facebook is not working. I am struggling with the FacebookOptions for Authority as I it keeps redirecting me to a Facebook page OAuth page which states "Sorry, something went wrong"
Here is how I defined my AddAuthentication method in Program.cs
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie(options =>
{
options.LoginPath = new PathString("/login");
options.AccessDeniedPath = new PathString("/denied");
})
.AddOpenIdConnect("google", googleOIDOptions =>
{
string gClientId = builder.Configuration.GetSection("Google:ClientId").Value;
string gClientPwd = builder.Configuration.GetSection("Google:ClientSecret").Value;
string gPath = builder.Configuration.GetSection("Google:CallbackPath").Value;
googleOIDOptions.Authority = "https://accounts.google.com";
googleOIDOptions.ClientId = gClientId;
googleOIDOptions.ClientSecret = gClientPwd;
googleOIDOptions.CallbackPath = gPath;
googleOIDOptions.SignedOutCallbackPath = "/google-signout";
})
.AddOpenIdConnect("facebook", fbOIDOptions =>
{
string fbClientId = builder.Configuration.GetSection("Facebook:ClientId").Value;
string fbClientPwd = builder.Configuration.GetSection("Facebook:ClientSecret").Value;
string fbPath = builder.Configuration.GetSection("Facebook:CallbackPath").Value;
fbOIDOptions.Authority = "https://www.facebook.com"; //"https://www.facebook.com/v16.0/dialog/oauth"; //;
//fbOIDOptions.Authority = "https://www.facebook.com/dialog/oauth";
fbOIDOptions.ClientId = fbClientId;
fbOIDOptions.ClientSecret = fbClientPwd;
fbOIDOptions.AccessDeniedPath = "/denied";
//fbOIDOptions.CallbackPath = fbPath;
//fbOIDOptions.SignedOutCallbackPath = "/facebook-signout";
//fbOIDOptions.Scope.Add("email");
//fbOIDOptions.ResponseType = OpenIdConnectResponseType.Code;
//fbOIDOptions.SaveTokens = true;
});
In my HomeController.cs I defined a common method to intake multiple providers as follows,
[HttpGet("login/{provider}")]
public IActionResult LoginExternal([FromRoute] string provider, [FromQuery] string returnUrl)
{
if (User != null && User.Identities.Any(identity => identity.IsAuthenticated))
{
RedirectToAction("", "Home");
}
returnUrl = string.IsNullOrEmpty(returnUrl) ? "/" : returnUrl;
var authenticationProperties = new AuthenticationProperties { RedirectUri = returnUrl };
return new ChallengeResult(provider, authenticationProperties);
}
If you check the developer console I see page redirection as shown below as per the HTTP 302 code,
but when I try to investigate further with client_id information passed it seems page has been permanently redirected as per the HTTP 301 error code to
https://facebook.com/dialog/oauth/?client_id=490605846573318&redirect_uri=https://localhost:7081/signin-oidc&response_type=id_token&scope=openid profile&response_mode=form_post&nonce=638109963739908338.ZWY0ZmM3NzMtNTM4Yy00YmVjLWJkOWEtYjY2NTMxZjRmYzNjZjg5NDRiYjQtNWU5Mi00NDI2LTg0NTQtYzVjZTAwM2FhMjdi&state=CfDJ8JX6wl_svOBCsCJLieG3tWwPbNfc9_vXYgF9hS8kM6eaFOb88LT-Baza9C3f5o-tn-7A5NYsI-rSTBiikzos3As_tkJxOrFzNGTWIgxmxdceVHim_mWudlhH-4fNCdCG2wZmWZPxHS7ES3FkoryXRSJ7wbVvIY3P4fDTpUsnJDfdl_z7i37VHkLighOHAfOXkKBAPYQTgibKJtd7WA0JC4V6j4_3xjNjeN12pBNtj0VGIeABxwwjQanAoiKb5yswfA73nr8ryEuF32Ikx4TE_s3_Mls7lIPrEWiRDEW5f7VhD246WhxKKEcinvUaMocGMA&x-client-SKU=ID_NETSTANDARD2_0&x-client-ver=6.15.1.0
After the above step the
I am not able to get what is causing this internal server error. Any clue or help is highly appreciated.
Thanks in advance
To access Facebook, a better approach is to replace AddOpenIdConnect with AddFaceBook(), that is more optimized for just Facebook authentication.
services.AddAuthentication().AddFacebook(facebookOptions =>
{
facebookOptions.AppId = configuration["Authentication:Facebook:AppId"];
facebookOptions.AppSecret = configuration["Authentication:Facebook:AppSecret"];
});
In theory AddOpenIDConnect should work, but AddFaceBook will make your life easier.
AddFaceBook is found in this NuGet package.
For more details, see Facebook external login setup in ASP.NET Core
7.0
Related
When I sign in with Microsoft OAuth in my Blazor app, authenticateResult.Succeeded is true, even if I don't specify a redirect URI. It's failing as intended for Google, if I don't add my URI to the OAuth client.
Imo it shouldn't work without that redirect URI, according to the OAuth2.0 spec:
The authorization server MUST require public clients and SHOULD
require confidential clients to register their redirection URIs.
I'm using Microsoft.AspNetCore.Authentication.MicrosoftAccount 3.0.3 with .NET Core 3.0
public class ExternalLoginModel : PageModel
{
public IActionResult OnGetAsync(string externalAuthType, string returnUrl)
{
var authenticationProperties = new AuthenticationProperties
{
RedirectUri = Url.Page("./externallogin",
pageHandler: "Callback",
values: new { returnUrl }),
};
return new ChallengeResult(externalAuthType, authenticationProperties);
}
public async Task<IActionResult> OnGetCallbackAsync(
string returnUrl = null, string remoteError = null)
{
var authenticateResult = await HttpContext.AuthenticateAsync("External");
if (!authenticateResult.Succeeded) // Should be false for Microsoft sign in
return BadRequest();
...
return LocalRedirect(returnUrl);
}
}
With the following added to my Startup:
services.AddAuthentication(o =>
{
o.DefaultSignInScheme = "External";
}).AddCookie("External");
services.AddAuthentication().AddGoogle(google =>
{
google.ClientId = Configuration["Authentication:Google:ClientId"];
google.ClientSecret = Configuration["Authentication:Google:ClientSecret"];
});
services.AddAuthentication().AddMicrosoftAccount(microsoftOptions =>
{
microsoftOptions.ClientId = Configuration["Authentication:Microsoft:ClientId"];
microsoftOptions.ClientSecret = Configuration["Authentication:Microsoft:ClientSecret"];
});
My App's Authentication settings look like this (I'm actually using localhost:12345 in the settings, but that's not what my app is running on..):
Ironically the last sentence might explain it, but I don't even know which flow the MicrosoftAccount library is using and I only get generic documentation when googling.
It fails as intended when using a completely different domain, not localhost with different ports. I guess that's good enough.
Additionally I unchecked "ID token" and "Treat application as a public client", therefore Authorization code flow should be used, to my understanding.
I've set up an asp.net core (2.1) application using asp.net identity and plugged in Identity Server 4 as the auth middleware. This is working well for local accounts, but now I'm trying to enable 3rd party login providers, starting with Google.
So I added the following to my ConfigureServices method:
services.AddAuthentication()
.AddGoogle("Google", options =>
{
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
options.ClientId = "numbershere.apps.googleusercontent.com";
options.ClientSecret = "my-secret";
});
And added the UI scaffolding in to the project and my Google Sign In button appears. If I click it, I get redirected to Google, log in and get presented with the confirmation screen before being returned to my app.
At this point, OnGetCallbackAsync of my ExternalLoginModel is being hit, which contains a call:
var info = await _signInManager.GetExternalLoginInfoAsync();
However, info is always null after this call and the user is therefore just redirected back to the login page:
public async Task<IActionResult> OnGetCallbackAsync(string returnUrl = null, string remoteError = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
if (remoteError != null)
{
ErrorMessage = $"Error from external provider: {remoteError}";
return RedirectToPage("./Login", new {ReturnUrl = returnUrl });
}
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
{
ErrorMessage = "Error loading external login information.";
return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
}
}
I've read about online and even followed the points in the Identity Server quick start, but can't see what I've missed.
Can anyone advise what I've not done/configured to make this work?
Thanks
Is there a package for authenticating with Azure AD, for Asp.net Core?
For example, the following Authentication packages exist, when querying Nuget:
Microsoft.AspNetCore.Authentication.Facebook
Microsoft.AspNetCore.Authentication.Twitter
Microsoft.AspNetCore.Authentication.Google
Microsoft.AspNetCore.Authentication.MicrosoftAccount
I tried to use the MicrosoftAccount package, but after wiring it up successfully, I got the following error from the Microsoft page:
Application '{my-app-id}' {my-app-name} is not supported over the /common or /consumers endpoints. Please use the /organizations or tenant-specific endpoint.
There's examples that don't use a middleware package, but the middleware package offers ease of use, and more directly integrates with the Identity framework.
Are there any direct packages that use Azure AD, or anyway to specify that the MicrosoftAccount package should point to the organizations/azure/tenant url?
This is what worked for me:
authBuilder
.AddMicrosoftAccount(Auth.Constants.AuthenticationScheme, options =>
{
options.ClientId = clientId;
options.ClientId = clientSecret;
if (tenantId != null)
{
var resource = "https://graph.microsoft.com";
options.AuthorizationEndpoint = $"https://login.microsoftonline.com/{tenantId}/oauth2/authorize?resource={resource}";
options.TokenEndpoint = $"https://login.microsoftonline.com/{tenantId}/oauth2/token?resource={resource}";
}
});
tenantId - look at the "Endpoints" section from the "App registrations" blade. You can extract it from the URLs, or use those URLs directly instead of what I've done above.
clientId - this is your "Application ID" in your registered app.
clientSecret - this is a password you create and register under the "Keys" section of your registered app.
You can then get back more information by using the access token with https://graph.microsoft.com, such as adding options like the following:
options.ClaimActions.DeleteClaim(ClaimTypes.Name);
options.ClaimActions.MapJsonKey(ClaimTypes.Name, "displayName");
options.ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "userPrincipalName");
options.Events = new OAuthEvents
{
OnCreatingTicket = async context =>
{
// Get the GitHub user
var request = new HttpRequestMessage(HttpMethod.Get, "https://graph.microsoft.com/v1.0/me/");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", context.AccessToken);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var response = await context.Backchannel.SendAsync(request, context.HttpContext.RequestAborted);
response.EnsureSuccessStatusCode();
var contents = await response.Content.ReadAsStringAsync();
var user = JObject.Parse(contents);
context.RunClaimActions(user);
}
};
I am using ASP.NET Web API and Google.Apis.Drive.v2 Client Library for .NET to upload files to users Drive.
All examples of using the Drive Client Library for .NET require a authentication flow. But how should I create the DriveService when I already know the access token?
Despite the fact that have been 2 years since the question has been asked, today I've encountered the same situation and my solution is:
var valid_token = "Pass_the_valid_token_here";
var token = new Google.Apis.Auth.OAuth2.Responses.TokenResponse()
{
AccessToken = valid_token,
ExpiresInSeconds = 3600,
Issued = DateTime.Now
};
var fakeflow = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = new ClientSecrets
{
ClientId = "fakeClientId",
ClientSecret = "fakeClientSecret"
}
});
UserCredential credential = new UserCredential(fakeflow, "fakeUserId", token);
var serviceInitializer = new BaseClientService.Initializer()
{
//ApplicationName = "Storage Sample",
HttpClientInitializer = credential
};
DriveService service = new DriveService(serviceInitializer);
Update
You could create your own custom token but the issue with this is going to be that the client library will not be able to refresh your access without the refresh token.
var token = new Google.Apis.Auth.OAuth2.Responses.TokenResponse()
{
AccessToken = valid_token,
ExpiresInSeconds = 3600,
Issued = DateTime.Now
};
var authorization = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = new ClientSecrets
{
ClientId = "lientId",
ClientSecret = "ClientSecret"
}
});
var credential = new UserCredential(authorization, "user", token);
The issue you are going to have with this is that the client library is not going to be able refersh the access token after it has expired since you are not supplying a refresh token its only going to work for an hour.
The answer from Svetoslav Georgiev has so far worked well for me - Can't thank you enough. Google really don't help themselves with the lack of .Net (Asp Core) samples etc. Anway, one problem I did run into was that of referer restriction, so a addition/slight modification to the answer - Once you have the "service" and want to say upload a file, you need to set the referer on a buried HttpClient property...
FilesResource.CreateMediaUpload uploadRequest;
byte[] byteArray = Encoding.UTF8.GetBytes(html);
using (var stream = new MemoryStream(byteArray))
{
uploadRequest = service.Files.Create(fileMetadata, stream, "text/html");
uploadRequest.Service.HttpClient.DefaultRequestHeaders.Referrer = new Uri($"{baseUrl}");
uploadRequest.Fields = "id";
var progress = uploadRequest.Upload();
if (progress.Exception != null)
{
throw progress.Exception;
}
var file = uploadRequest.ResponseBody;
.... do what you will with file ....
}
I've implemented simple Google+ authentication on my MVC5 app and I'd like to access their google calendar. How do I do this using the MVC identity system and my already authenticated user?
Dim authGOps = New GooglePlusAuthenticationOptions() With {
.Caption = "Google+",
.ClientId = "MYCLIENTRID",
.ClientSecret = "MYCLIENTSECRET",
.Provider = New GooglePlusAuthenticationProvider() With {
.OnAuthenticated = Async Function(context)
context.Identity.AddClaim(New Claim(GooglePlusAccessTokenClaimType, context.AccessToken))
End Function
}
}
authGOps.Scope.Add("https://www.googleapis.com/auth/calendar")
app.UseGooglePlusAuthentication(authGOps)
Getting the calendar service:
Dim calendarService = New CalendarService(New Google.Apis.Services.BaseClientService.Initializer() With {
WHAT GOES HERE TO AUTHENTICATE USING MY OLD AUTH CEDENTIALS?
}
So I as well would love to use the Service as it's documented almost everywhere, but I found a workaround to at least getting the data and not having to login again.
Make sure to Nuget Json.Net to deserialize and strongly type. Otherwise you'll get a Json string to manage.
It's in C#, but I'm sure the translation won't be too difficult. Hope it helps!
var claimsIdentity = User.Identity as ClaimsIdentity;
var claims = claimsIdentity.Claims;
var accessTokenClaim = claims.FirstOrDefault(x => x.Type == GooglePlusAccessTokenClaimType);
if (accessTokenClaim != null)
{
string calendarUrl = "https://www.googleapis.com/calendar/v3/users/me/calendarList?access_token=" + Uri.EscapeDataString(accessTokenClaim.Value);
using(var client = new HttpClient())
{
var response = await client.GetAsync(calendarUrl);
var calendarList = JsonConvert.DeserializeObject<CalendarList>(await response.Content.ReadAsStringAsync());
}
}