Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

.NET How to NOT authenticate if provided user from OIDC is not available in database

So my .NET Core WebApplication is logging in an user via Azure AD and I have a database with users and their roles.

I created already OIDC MiddleWare to add Claims from my database for the user that tries to log in.

So the flow is:

  • I login with my AD account
  • I get the email address and check in the database for it's role
  • Now if the user is blocked or not assigned, the login should fail

So my question: Is there a way how I could deny the authentication for the user, when he is not available in the DB or blocked?

What I did now is that I set a Claim and if that claim is not available it will be redirected to Access Denied page (by using the AuthorizationPolicy), but I want that the user will be redirected to the login page from Microsoft/AD (in best case with a message).

Is that possible somehow and if so, how ?

This is my code now:

 services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
     .AddAzureAD(options => { Configuration.Bind("AzureAd", options); }
 );

 services.AddControllersWithViews(options =>
 {
   var policy = new AuthorizationPolicyBuilder()
                .RequireAuthenticatedUser()
                .RequireAssertion(context =>
                {
                    Claim claim = context.User.Claims.First(claim => claim.Type == ClaimTypes.Expired);
                    return context.User.HasClaim(x => x.Type == ClaimTypes.Expired) &&
                           context.User.Claims.First(claim => claim.Type == ClaimTypes.Expired).Value
                               .Equals("false", StringComparison.OrdinalIgnoreCase);
                })
                .RequireClaim(ClaimTypes.Name)
                .RequireClaim(ClaimTypes.Role)
                .Build();
            options.Filters.Add(new AuthorizeFilter(policy));
        });

 // OIDC Middleware, to access the User's Claims while logging in through AzureAD
        services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options =>
        {
            options.Events = new OpenIdConnectEvents
            {
                OnRemoteFailure = ctx =>
                {
                    ctx.Response.Redirect("/");
                    ctx.HandleResponse();
                    return Task.CompletedTask;
                },
                OnSignedOutCallbackRedirect = ctx =>
                {
                    ctx.Response.Redirect("/");
                    ctx.HandleResponse();
                    return Task.CompletedTask;
                },
                OnTokenValidated = ctx =>
                {
                    // Get the user's email 
                    var email = ctx.Principal.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Name)?.Value;

                    // Query the database to get the role
                    using (var db = ctx.HttpContext.RequestServices.GetRequiredService<TracingContext>())
                    {
                        // Get the Users from the database, with the logged in email address (from Azure)
                        User user = db.Users.FirstOrDefault(u => u.UPN.Equals(email));

                        if (user != null)
                        {
                            user.LastLogin = DateTime.Now;
                            db.SaveChanges();

                            // Add claims
                            var claims = new List<Claim>
                            {
                                new Claim(ClaimTypes.Role, user.Role.ToString()),
                                new Claim(ClaimTypes.Expired, (!user.IsActivated || user.IsBlocked).ToString())
                            };

                            // Save the claim
                            var appIdentity = new ClaimsIdentity(claims);
                            ctx.Principal.AddIdentity(appIdentity);
                        }
                        else
                        {
                           **// Send back to Login Page (with error message, maybe?)**
                        }
                    }
                    return Task.CompletedTask;
                },
            };
        });
like image 734
bbrinck Avatar asked Oct 28 '22 03:10

bbrinck


1 Answers

You can override the default processing and handle the response yourself:

// Send back to Login Page (with error message, maybe?)
ctx.HandleResponse();
ctx.Response.Redirect("/path/to/login");

The call to HandleResponse signals that we want to handle the response ourselves and the following call sets up the redirect. There are a few approaches to sending over an error message. One approach is to provide a query-string parameter to the login URL.

like image 66
Kirk Larkin Avatar answered Nov 15 '22 06:11

Kirk Larkin