Google OAuth Issue - umbraco

I have a Umbraco website that has google sign in button configured as follows:
At the top of the page (inside the header section) I have the scripts for calling google API:
<script src="https://apis.google.com/js/client:platform.js?onload=start" async defer></script>
<script>
function start() {
gapi.load('auth2', function() {
auth2 = gapi.auth2.init({
client_id: '<myapp client Id>.apps.googleusercontent.com',
// Scopes to request in addition to 'profile' and 'email'
redirect_uri: 'http://localhost:40136/umbraco/Surface/AuthSurface/GoogleAuthrizedUser',
scope: 'profile email'
});
});
}
</script>
In the body section of the code I have the google button setup and associated click function:
<script>
function onSignIn(authResult) {
if (authResult['code']) {
var authCode = authResult['code'];
console.log("Authorization Code: " + authCode);
$.post("/umbraco/Surface/AuthSurface/GoogleAuthrizedUser", { code: authCode })
.done(function(msg) {
// Success settings
})
.fail(function(xhr, status, error) {
});
} else {
//authResult['code'] is null
//handle the error message.
}
};
</script>
Controller code that handles the call back on the server end:
public class AuthSurfaceController : SurfaceController
{
public ActionResult GoogleAuthrizedUser()
{
string AuthCode = HttpContext.Request["code"];
var info = new GoogleAccessTokenResponse();
var client = new GoogleOAuthClient();
try
{
info = client.GetAccessTokenFromAuthorizationCode(AuthCode);
}
catch (Exception ex)
{
var strMessage = String.Format("<div class=\"info\"><p>{0}</p><p>{1}</p></div>", "Google Login Error",
ex.Message);
return Json(new AjaxOperationResponse(false, strMessage));
}
}
}
On the Serverside I am using Skybrud Social plugin for accessing google apis.
The google authentication happens in the popup and authorizes client with credentials and authResult['code'] has a valid code.
In the controller when I initialize the client and call the function GetAccessTokenFromAuthorizationCode(AuthCode), it returns an exception of 'Invalid Request'
I tried checking this authResult['code'] returned in the javascript function onSignIn in the https://developers.google.com/oauthplayground/
Same error description is shown 'Invalid request'. I am not sure why this is happening. The error returned is "invalid_grant"
Can anyone have a solution to this problem? What am I doing wrong here?

In your surface controller you're initializing a new instance of GoogleOAuthClient, but without setting any of the properties. The GetAccessTokenFromAuthorizationCode method requires the ClientId, ClientSecret and RedirectUri properties to have a value. You can initialize the properties like this:
// Initialize a new instance of the OAuth client
GoogleOAuthClient oauth = new GoogleOAuthClient {
ClientId = "The client ID of your project",
ClientSecret = "The client secret of your project",
RedirectUri = "The return URI (where users should be redirected after the login)"
};
You can read more about authentication in the documentation: http://social.skybrud.dk/google/authentication/ (the approach explained there will however not use any JavaScript)

Related

NextAuth.js callbackUrl and first attempt after sign in

I am implementing NextAuth authentication and there is something that remains unclear for me. I explain.
To perform authentication with CredentialsProviders and signIn() well (means avoid the session's status unauthenticated during the first attempt). The doc gives a partial solution which is:
You can use useSession() in a way that makes sure you always have a valid session
I use the default behavior to satisfy this advice by implementing my signIn() method like this
const login_user = async () => {
const response = await signIn("Credentials", {
redirect: false,
username: username,
password: password,
});
if (response?.error) {
setError(response.error);
} else {
setError(null);
}
//If user signed successfully we redirect to the dashboard page
if (response.url && response.ok === true) {
router.push("/dashboard/general");
}
};
and it works well.
Now always in the doc, they say that we may add callbackUrl to signIn() which is the url where you want to redirect the user after successful sign in.
I configure my redirect callback in [...nextauth].ts file like this
redirect: async ({ url, baseUrl }) => {
return url.startsWith(baseUrl)
? Promise.resolve(url)
: Promise.resolve(baseUrl);
},
and modify signIn() method by adding it callbackUrl like below in the react component.
const login_user = async () => {
const response = await signIn("Credentials", {
redirect: false,
username: username,
password: password,
callbackUrl: "/dashboard/general",
});
if (response?.error) {
setError(response.error);
} else {
setError(null);
}
};
But when a user sign in, he is not redirected.
My question is:
What is the role of callbackUrl at that place if after the sign in or first attempt of sign in it cannot redirect the user to the specified callbackUrl?
I know the importance of callbackUrl about security against attacker)
May be I do not understand well that notion.
Can someone explains it?

How do I create a 'prettier' URL for OpenIddict endpoints in .NET Core

I'm working with an angular front end connected to a .NET Core back end and using OpenIddict for authorization. When I land on my login page, the url looks as follows:
https://localhost:44340/Account/Login?ReturnUrl=%2Fconnect%2Fauthorize%3Fresponse_type%3Dcode%26client_id%3DclientIDExample%26state%3DYUVBdDJvUG04SUpVTzZqSEJvRlMxWFZnWU0xSUVsSi1IVnR1WEY2R3pCMG1m%26redirect_uri%3Dhttp%253A%252F%252Flocalhost%253A4200%26scope%3Dopenid%2520profile%2520email%2520offline_access%26code_challenge%3DAg0TCRqJBaFqpa8sJb--J67Yd88tNPmouGonUvBbBbM%26code_challenge_method%3DS256%26nonce%3DYUVBdDJvUG04SUpVTzZqSEJvRlMxWFZnWU0xSUVsSi1IVnR1WEY2R3pCMG1m
Here is the 'user friendly' url I want the users to see, not the authorize endpoint:
https://localhost:44340/Account/Login
Here is the part of my Authorization code that I'm hitting:
[HttpGet("~/connect/authorize")]
[HttpPost("~/connect/authorize")]
[IgnoreAntiforgeryToken]
public async Task<IActionResult> Authorize()
{
var request = HttpContext.GetOpenIddictServerRequest() ??
throw new InvalidOperationException("The OpenID Connect request cannot be retrieved.");
// Retrieve the user principal stored in the authentication cookie.
// If it can't be extracted, redirect the user to the login page.
var result = await HttpContext.AuthenticateAsync(IdentityConstants.ApplicationScheme);
if (result is null || !result.Succeeded)
{
// If the client application requested promptless authentication,
// return an error indicating that the user is not logged in.
if (request.HasPrompt(Prompts.None))
{
return Forbid(
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
properties: new AuthenticationProperties(new Dictionary<string, string>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.LoginRequired,
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The user is not logged in."
}));
}
return Challenge(
authenticationSchemes: IdentityConstants.ApplicationScheme,
properties: new AuthenticationProperties
{
RedirectUri = Request.PathBase + Request.Path + QueryString.Create(
Request.HasFormContentType ? Request.Form.ToList() : Request.Query.ToList())
});
}
}
Here is my Startup class:
public class Startup
{
public Startup(IConfiguration configuration, IHostEnvironment env)
{
Configuration = configuration;
_env = env;
}
public IConfiguration Configuration { get; }
public IHostEnvironment _env { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
var connectionString = Configuration[$"Connections:DefaultConnection"];
var EncryptionCertificate = new X509Certificate2(Convert.FromBase64String(Configuration["EncCert"]), (string)null, X509KeyStorageFlags.MachineKeySet);
var SignCertificate = new X509Certificate2(Convert.FromBase64String(Configuration["SigCert"]), (string)null, X509KeyStorageFlags.MachineKeySet);
services.AddRazorPages();
//DbContext OnConfiguring gets done here
services.AppDataContext(connectionString);
// OpenIddict offers native integration with Quartz.NET to perform scheduled tasks
// (like pruning orphaned authorizations/tokens from the database) at regular intervals.
services.AddQuartz(options =>
{
options.UseMicrosoftDependencyInjectionJobFactory();
options.UseSimpleTypeLoader();
options.UseInMemoryStore();
});
// Register the Quartz.NET service and configure it to block shutdown until jobs are complete.
services.AddQuartzHostedService(options => options.WaitForJobsToComplete = true);
services.AddIdentity<ApplicationUserModel, IdentityRole>()
.AddEntityFrameworkStores<DataContext>()
.AddDefaultTokenProviders();
services.Configure<IdentityOptions>(options =>
{
// Configure Identity to use the same JWT claims as OpenIddict instead
// of the legacy WS-Federation claims it uses by default (ClaimTypes),
// which saves you from doing the mapping in your authorization controller.
options.ClaimsIdentity.UserNameClaimType = Claims.Name;
options.ClaimsIdentity.UserIdClaimType = Claims.Subject;
options.ClaimsIdentity.RoleClaimType = Claims.Role;
// Note: to require account confirmation before login,
// register an email sender service (IEmailSender) and
// set options.SignIn.RequireConfirmedAccount to true.
//
// For more information, visit https://aka.ms/aspaccountconf.
options.SignIn.RequireConfirmedAccount = false;
});
services.IdentityServer(EncryptionCertificate, SignCertificate);
services.AddAuthentication(Configuration);
//Adds some claim data
services.AddScoped<IUserClaimsPrincipalFactory<ApplicationUserModel>, AdditionalUserClaimsPrincipalFactory>();
services.AddCors(options => options.AddPolicy("AllowCors",
builder =>
{
builder.SetIsOriginAllowed(_ => true)
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials();
})
);
if (_env.IsDevelopment())
{
//Script will populate Database but should be scripted for production
services.AddHostedService<Worker>();
}
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCors("AllowCors");
//Middleware that takes care of authorization and authentication
//Should always happen before the endpoints
//These methods allow for decorating Controllers with the Authorize attribute, which controls page and feature access
app.UseAuthentication();
app.UseAuthorization();
//Endpoints instead of Razor pages
//Because using APIs and Angular
app.UseEndpoints(endpoints =>
{
//endpoints.MapControllers();
endpoints.MapDefaultControllerRoute();
});
}
}
Do I need a rewrite? A redirect? I need to retain the endpoint but I can't be showing the users the entire endpoint.
I think most system will present you with that 'ugly' link, event if you try to login with Google or Facebook, you will be presented with the same long link.
There are ongoing work in the community that could result in nicer links and one is Pushed Authorization Requests (PAR), but I doubt all token providers supports it and I don't know what the support is for that in ASP.NET Core today.
I ended up having to store the long endpoint in a helper so i could pass the endpoint but get a nice looking ReturnUri, which I'm now passing as 'Home':
New link is:
https://localhost:44340/Account/Login?ReturnUrl=Home
[HttpGet("~/connect/authorize")]
[HttpPost("~/connect/authorize")]
[IgnoreAntiforgeryToken]
public async Task<IActionResult> Authorize()
{
var request = HttpContext.GetOpenIddictServerRequest() ??
throw new InvalidOperationException("The OpenID Connect request cannot be retrieved.");
if (_urlHelper != null)
{
_urlHelper.Value.urlEndpoint = Request.PathBase + Request.Path + QueryString.Create(
Request.HasFormContentType ? Request.Form.ToList() : Request.Query.ToList());
}
// Retrieve the user principal stored in the authentication cookie.
// If it can't be extracted, redirect the user to the login page.
var result = await HttpContext.AuthenticateAsync(IdentityConstants.ApplicationScheme);
if (result is null || !result.Succeeded)
{
// If the client application requested promptless authentication,
// return an error indicating that the user is not logged in.
if (request.HasPrompt(Prompts.None))
{
return Forbid(
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
properties: new AuthenticationProperties(new Dictionary<string, string>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.LoginRequired,
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The user is not logged in."
}));
}
return Challenge(
authenticationSchemes: IdentityConstants.ApplicationScheme,
properties: new AuthenticationProperties
{
RedirectUri = "Home"
});
}`

Using ASP.NET identity to do external oauth logins, how can add parameters to the facebook authorization endpoint? I want to pass "display=popup"

I'm using ASP.NET MVC 6 (.net core). With it, i'm using the built in external login logic in order to authenticate with facebook.
I've made a modification to it so that instead of authenticating within the same window, i'm launching a popup and authenticating there. Once successful, the popup closes itself and tells my main window to redirect. This all works.
However, I want to use the "smaller/mini" version of the facebook login page. This can be seen here:
https://www.facebook.com/login.php?display=popup
"display=popup" is what is controlling it.
I don't see how i can inject this kvp in my C# code. Where can i do it?
app.UseFacebookAuthentication(new FacebookOptions
{
// was hoping for something here... tried to stick it into the authorizationurl but then i end up with 2 question marks and it fails
AppId = "blah",
AppSecret = "blah"
});
[AllowAnonymous]
public IActionResult ExternalLogin(string provider, string returnUrl = null)
{
var redirectUrl = Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl });
var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
// Don't see anything here...
return Challenge(properties, provider);
}
You can use OnRedirectToAuthorizationEndpoint event:
var facebookOptions = new FacebookOptions
{
AppId = "",
AppSecret = "",
Events = new OAuthEvents()
{
OnRedirectToAuthorizationEndpoint = ctx =>
{
ctx.HttpContext.Response.Redirect(ctx.RedirectUri + "&display=popup&pip");
return Task.FromResult(0);
}
}
};
app.UseFacebookAuthentication(facebookOptions);

how to send authenticated ajax call to web API

I have a Web Api Application which has the following question.
[HttpGet]
[Route("Account/userName{userName}/password={password}/rememberMe/{rememberMe}")]
public HttpResponseMessage LogIn(string userName, string password, bool rememberMe)
{
if (User.Identity.IsAuthenticated)
{
return Request.CreateResponse(HttpStatusCode.Conflict, "already logged in.");
}
var dbPerson = dbContext.Persons.Where(x => x.UserName.Equals(userName) && x.EncryptedPassword.Equals(password)).FirstOrDefault();
if (dbPerson != null)
{
FormsAuthentication.SetAuthCookie(userName, rememberMe);
return Request.CreateResponse(HttpStatusCode.OK, "logged in successfully");
}
else
{
return new HttpResponseMessage(HttpStatusCode.Unauthorized);
}
}
I am calling from another MVC project. I Got the authentication but very next page where I am calling the ajax method
var uri = 'http://localhost:44297/api/XXXX';
$(document).ready(function () {
// Send an AJAX request
$.getJSON(uri)
.done(function (data) {
// On success, 'data' contains a list of products.
for (var i = 0; i < data.$values.length; i++)
{
}
})
.fail(function() {
console.log( "error" )});
});
I am getting GET http://localhost:44297/api/StudyFocus 401 (Unauthorized). how I can solve this issue. I know I need to pass some cookie/session value with this ajax call. but I don't know how. can anyone explain me with example.
My application relies on web Api project including authentication. I need to make web api application secure using form authentication. Any help is highly appreciable. Thanks
You can't authenticate web api by the use of cookies or session. You need access token to do that.
Follow this tutorial for the implementation http://www.asp.net/web-api/overview/security/individual-accounts-in-web-api

RestSharp calling WebAPI with Thinktecture AuthenticationConfiguration

I am using Restsharp within an MVC app, trying to call a backend MVC WebAPI protected by Thinktecture IdentityModel AuthenticationConfiguration.
MVC API Setup
My MVC API test is setup with the below:
private static void ConfigureAuth(HttpConfiguration config)
{
var authConfig = new AuthenticationConfiguration
{
DefaultAuthenticationScheme = "Basic",
EnableSessionToken = true,
SendWwwAuthenticateResponseHeader = true,
RequireSsl = false,
ClaimsAuthenticationManager = new AddCustomClaims(),
SessionToken = new SessionTokenConfiguration
{
EndpointAddress = "/token",
SigningKey = Convert.ToBase64String(CryptoRandom.CreateRandomKey(32)),
DefaultTokenLifetime = new TimeSpan(1, 0, 0)
}
};
authConfig.AddBasicAuthentication((username, password) =>
{
return username == "admin" && password == "password";
});
config.MessageHandlers.Add(new AuthenticationHandler(authConfig));
}
private static void ConfigureCors(HttpConfiguration config)
{
var corsConfig = new WebApiCorsConfiguration();
config.MessageHandlers.Add(new CorsMessageHandler(corsConfig, config));
corsConfig
.ForAllOrigins()
.AllowAllMethods()
.AllowAllRequestHeaders();
}
Javascript works OK
I know 100% the token I am sending with Restsharp is correct and working with equivalent json calls (the token used in the javascript is the same used in the Web MVC controller as its stored in the Session array):
var authToken = config.authToken,
baseUri = config.baseUri,
configureRequest = function (xhr) {
xhr.setRequestHeader("Authorization", "Session " + authToken);
},
errorHandler = function (xhr, status, error) {
if (xhr.status === 401 && config.onAuthFail) {
config.onAuthFail(xhr, status, error);
}
};
Calling the API from my MVC web front end client app - Authorization has been denied for this request
Then in my MVC app controller action i use RestSharp as follows:
public ActionResult Test()
{
var token = Session[Constants.SessionTokenKey] as string;
var client = new RestClient(new Uri("http://localhost:65104/"));
var request = new RestRequest("contacts", Method.GET);
string authHeader = System.Net.HttpRequestHeader.Authorization.ToString();
request.AddHeader(authHeader, string.Format("Authorization Session {0}", token));
var json = client.Execute(request);
// break point here checking the status it has been denied
return View("Index");
}
Checking the status, it returns "{\"message\":\"Authorization has been denied for this request.\"}".
I have tried adding the token with Restsharp request methods with request.AddHeader(authHeader, string.Format("Authorization Session {0}", token)); and also with request.AddHeader(authHeader, string.Format("JWT {0}", token));, but get the same access denied for both ways.
What am I doing wrong please or any recommendations on where to look?
Looks like your JavaScript code and RestSharp request code doesn't match.
In JS you set a header with name Authorization and give it a value Session sometoken:
xhr.setRequestHeader("Authorization", "Session " + authToken);
In RestSharp you assign a header with name Authorization a value Authorization Session sometoken
request.AddHeader(authHeader, string.Format("Authorization Session {0}", token));
So I would suggest changing your RestSharp AddHeader code to this:
request.AddHeader(authHeader, string.Format("Session {0}", token));

Resources