3
\$\begingroup\$

I am trying to implement Oauth in my .net API with EntityFramework. I already completed the custom local authentication with JWT tokens and refresh tokens. I got a little bit confused as to how I would approach it.

Here is my implementation for the Google Oauth system (I also use Facebook Oauth but it's kinda the same thing):

[HttpGet("google-login")] [AllowAnonymous] public IActionResult GoogleLogin([FromQuery] string returnUrl) { var redirectUrl = Url.Action(nameof(GoogleResponse), "Auth", new { returnUrl }, Request.Scheme); var properties = new AuthenticationProperties { RedirectUri = redirectUrl }; return Challenge(properties, GoogleDefaults.AuthenticationScheme); } [HttpGet("signin-google")] [AllowAnonymous] public async Task<IActionResult> GoogleResponse([FromQuery] string returnUrl) { var authenticateResult = await HttpContext.AuthenticateAsync(GoogleDefaults.AuthenticationScheme); if (!authenticateResult.Succeeded) return BadRequest("Google authentication failed."); var claims = authenticateResult.Principal.Identities.FirstOrDefault()?.Claims; var email = claims?.FirstOrDefault(c => c.Type == ClaimTypes.Email)?.Value; var name = claims?.FirstOrDefault(c => c.Type == ClaimTypes.Name)?.Value; var key = claims?.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value; var ipAddress = HttpContext.Connection.RemoteIpAddress.MapToIPv6().ToString(); if (string.IsNullOrEmpty(email)) return BadRequest("Email not found"); var result = await authService.SignInWithProviderAsync(email, key, ipAddress, "google"); return result.Match<IActionResult, OauthResponse>(success => { var result = success.Data; SetAccessTokenInResponse(result.Jwt); SetRefreshTokenInResponse(result.RefreshToken); var redirectUri = $"{returnUrl}?access_token={result.Jwt}&refresh_token={result.RefreshToken}"; return Redirect(redirectUri); }, BadRequest); } 

and this is the program.cs setting for oauth:

builder.Services.AddAuthentication(options => { options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }).AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = builder.Configuration["JwtConfig:Issuer"], ValidAudience = builder.Configuration["JwtConfig:Audience"], IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["JwtConfig:Key"])) }; options.Events = new JwtBearerEvents { OnMessageReceived = context => { context.Token = context.Request.Cookies["tmy209w1"]; return Task.CompletedTask; } }; }).AddGoogle(options => { options.ClientId = builder.Configuration["Authentication:Google:ClientId"]; options.ClientSecret = builder.Configuration["Authentication:Google:ClientSecret"]; options.CallbackPath = "/signin-google"; options.SaveTokens = false; }).AddFacebook(options => { options.ClientId = builder.Configuration["Authentication:Facebook:AppId"]; options.ClientSecret = builder.Configuration["Authentication:Facebook:AppSecret"]; options.CallbackPath = "/signin-facebook"; options.SaveTokens = false; }); 

I am not sure if this is how it's supposed to go so correct me if anything is wrong with the implementation.

I don't want to use the token / cookie that the Oauth middleware issues because I already have a custom token that I want to issue to the user, but I keep finding this persistent cookie name identity.external and I don't know why it's persisting.

Please help me get this to work properly the way it's meant to work.

\$\endgroup\$
3
  • 4
    \$\begingroup\$If your code isn't working the way you expect, this is more of a Stack Overflow question than a Code Review question.\$\endgroup\$
    – Chris
    CommentedFeb 3 at 23:09
  • 1
    \$\begingroup\$@Chris thanks for replying , the code is running correctly I am just questioning some aspects , like if I am implementing this the right way and some issues I am facing\$\endgroup\$CommentedFeb 3 at 23:15
  • \$\begingroup\$You can use ConfigureApplicationCookie and ConfigureExternalCookie to configure cookies.\$\endgroup\$
    – iSR5
    CommentedFeb 4 at 6:03

1 Answer 1

3
\$\begingroup\$

Route templates

The google-login and signin-google seem a bit weird to me:

  • login and sign-in are usually considered as synonyms (or least used interchangeably)
  • they follow different naming conventions even though they are tightly coupled

Try to use the same naming conventions (like prefix both routes with google-) and use more expressive suffixes.

The same could/should be applied for the method names as well.

Usage of HttpContext

For better testability rely on the IHttpContextAccessor to access the HttpContext rather than through the ControllerBase

Null conditional operator

I personally don't like to use multiple ?. in a single line. I would rather suggest an early exit than having null conditional operators everywhere.

everywhere meme

Constructing URl

var redirectUri = $"{returnUrl}?access_token={result.Jwt}&refresh_token={result.RefreshToken}"; 

If possible, do not construct URL by hand. It is error-prone, for example if your returnUrl already contains a query string then this string concatenation will generate an invalid url.

Instead use UriBuilder and HttpUtility.ParseQueryString

var uriBuilder = new UriBuilder(returnUrl); var query = HttpUtility.ParseQueryString(uriBuilder.Query); query["access_token"] = result.Jwt; query["refresh_token"] = "result.RefreshToken"; uriBuilder.Query = query.ToString(); return Redirect(uriBuilder.ToString()); 
\$\endgroup\$

    Start asking to get answers

    Find the answer to your question by asking.

    Ask question

    Explore related questions

    See similar questions with these tags.