Skip to content

Accessing AuthenticationStateProvider from DbContext or Startup.cs #30982

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
mrlife opened this issue Mar 16, 2021 · 18 comments
Closed

Accessing AuthenticationStateProvider from DbContext or Startup.cs #30982

mrlife opened this issue Mar 16, 2021 · 18 comments
Labels
area-blazor Includes: Blazor, Razor Components Docs This issue tracks updating documentation feature-blazor-server feature-blazor-server-auth ✔️ Resolution: Answered Resolved because the question asked by the original author has been answered. question Status: Resolved
Milestone

Comments

@mrlife
Copy link
Contributor

mrlife commented Mar 16, 2021

Is it possible to access Blazor's AuthenticationStateProvider from a DbContext or in Startup.Configure()?

I receive this error:

GetAuthenticationStateAsync was called before SetAuthenticationState.

@Blackleones, did you ever get this line to work in your DbContext? ref #17442 (comment)

var userId = await _currentUserService.GetUserId();
@mrlife mrlife changed the title Accessing AuthenticationStateProvider from DbContext Accessing AuthenticationStateProvider from DbContext or Startup.cs Mar 16, 2021
@mkArtakMSFT mkArtakMSFT added the area-auth Includes: Authn, Authz, OAuth, OIDC, Bearer label Mar 16, 2021
@blowdart blowdart added area-blazor Includes: Blazor, Razor Components and removed area-auth Includes: Authn, Authz, OAuth, OIDC, Bearer labels Mar 17, 2021
@javiercn
Copy link
Member

@mrlife thanks for contacting us.

I'm not sure what you are trying to achieve, but the answer in general is no, since it needs to be accessed within the scope/lifetime of a Blazor application.

@mrlife
Copy link
Contributor Author

mrlife commented Mar 17, 2021

@javiercn Thanks for getting back to me. What ultimately may help is to understand why accessing a scoped service in Startup.Configure() appears to be a different instance of the service that is created when the Blazor app is loaded. I have a Guid that's set in the service's constructor, and it's different when accessed within the Blazor workflow and within Startup.Configure(). Aren't scoped services created once per HTTP request?

@javiercn
Copy link
Member

@mrlife Blazor creates its own scope every time a new session is established (when you start a circuit) and is not associated with an HTTP request. You can only operate within the context of the Blazor scope either inside a component or a circuit handler.

In addition to that, If your component inherits from OwningComponentBase<T> the T in the Service property has it's own independent scope.

@mrlife
Copy link
Contributor Author

mrlife commented Mar 17, 2021

@javiercn Thank you for letting me know about these specifics of service scope in a Blazor application. I didn't get out of the docs what you explained... perhaps it's a good candidate to be added there?

@mkArtakMSFT mkArtakMSFT added the Docs This issue tracks updating documentation label Mar 18, 2021
@mkArtakMSFT mkArtakMSFT added this to the Backlog milestone Mar 18, 2021
@ghost
Copy link

ghost commented Mar 18, 2021

We've moved this issue to the Backlog milestone. This means that it is not going to be worked on for the coming release. We will reassess the backlog following the current release and consider this item at that time. To learn more about our issue management process and to have better expectation regarding different types of issues you can read our Triage Process.

@mkArtakMSFT
Copy link
Member

Thanks for your feedback, @mrlife.
Moving this to Backlog so that we can evaluate later whether the current documentation is misleading for others too or not. If yes, we will try to improve it then.

@Bobetti
Copy link

Bobetti commented Mar 8, 2022

I don't know where to write, but I have a similar problem in Blazor Server application, so I will describe my situation.

In short -> I need to get logged in user Id from dbcontext, why ->

Inside dbcontext class I have the following method:

public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken())
       {            

           foreach (var entry in ChangeTracker.Entries<AuditableEntity>())
           {
               switch (entry.State)
               {
                   case EntityState.Added:
                       entry.Entity.CreatedBy = _loggedUserService.UserId;
                       entry.Entity.Created = _dateTime.Now;
                       break;
                   case EntityState.Modified:
                       entry.Entity.LastModifiedBy = _loggedUserService.UserId;
                       entry.Entity.LastModified = _dateTime.Now;
                       break;
               }
           }
           var res = await base.SaveChangesAsync(cancellationToken);            

           return res;
       }

loggedUserService previously worked fine and looks like this ->

public class LoggedInUserService : ILoggedInUserService
   {
       public LoggedInUserService(IHttpContextAccessor httpContextAccessor)
       {
           var id = httpContextAccessor.HttpContext?.User?.FindFirstValue(ClaimTypes.NameIdentifier);
    if (!string.IsNullOrEmpty(id))
           {
               UserId = Guid.Parse(id);
           }
       }

       public Guid? UserId { get; }
   }

Now, in .Net 6 - httpContextAccessor is not working anymore.

Rewriting the loggedUserService to start using AuthenticationStateProvider doesn't bring results because I get the exception in this case: GetAuthenticationStateAsync was called before SetAuthenticationState.

So, what should I do? Any help?

@mrlife
Copy link
Contributor Author

mrlife commented Mar 15, 2022

Not having a way to access user-level data on code that runs on the server (outside of Blazor's scope) is an ongoing issue that I hope will be addressed one day.

@SteveSandersonMS @danroth27 @shanselman

@danroth27
Copy link
Member

@mrlife @Bobetti The user identity established on the server is typically stored on the HttpContext. AuthenticationStateProvider is used for getting the authentication state on the client, which doesn't operate in the context of a request.

What prevents you from flowing the user from the HttpContext to where you need to do your user specific database queries?

@mrlife
Copy link
Contributor Author

mrlife commented Mar 18, 2022

Hi @danroth27, I really appreciate you jumping in on this. I am currently pulling the user from the initial HttpContext into Blazor's scope through Host.cshtml.cs -> App.razor. That is working great.

The issue is kind of in the other direction. With the Audit.NET audit framework package, there is a configuration that's set up once in Startup.cs. There's code in that configuration that runs on the server after EF Core's SaveChanges completes that needs to access the user in order to log who made the change. Currently, the only option is to use HttpContextAccessor, which is not recommended because it is not guaranteed to work in Blazor apps.

@mrlife
Copy link
Contributor Author

mrlife commented May 6, 2022

@danroth27 Possible to have an official response so we know whether to hold out or look for an alternative to Audit.NET?

@javiercn
Copy link
Member

javiercn commented May 6, 2022

@mrlife You can't access the AuthenticationStateProvider in Startup.

AuthenticationStateProvider is a scoped service that is only valid in the context of the Blazor session (circuit).

The issue is similar as trying to access the HttpContext outside of an HTTP request using IHttpContextAccessor. At that time the service is not setup correctly.

@javiercn javiercn added question ✔️ Resolution: Answered Resolved because the question asked by the original author has been answered. labels May 6, 2022
@ghost ghost added the Status: Resolved label May 6, 2022
@mrlife
Copy link
Contributor Author

mrlife commented May 6, 2022

@javiercn Thanks for writing. I'm not asking for AuthenticationStateProvider to be available in Startup, explicitly. Any solution would be helpful.

Not having a way to access user-level data on code that runs on the server (outside of Blazor's scope) is an ongoing issue that I hope will be addressed one day.

@javiercn
Copy link
Member

javiercn commented May 6, 2022

@mrlife what you are asking is fundamentally impossible. There are not users when Startup is running.

I'm not sure what you are trying to accomplish here. The only thing that you could do today to access user-level data from outside of the circuit scope would be to register a singleton service on DI and have the circuit (maybe via a circuit handler) notify that service that a new circuit has been created.

If that's what you are looking for, that's something you can already do today and not something we plan to offer out of the box.

@danroth27
Copy link
Member

The issue is kind of in the other direction. With the Audit.NET audit framework package, there is a configuration that's set up once in Startup.cs. There's code in that configuration that runs on the server after EF Core's SaveChanges completes that needs to access the user in order to log who made the change. Currently, the only option is to use HttpContextAccessor, which is not recommended because it is not guaranteed to work in Blazor apps.

@mrlife Does your configured Audit.NET code always run in the context of a request? If it does, then I think using IHttpContextAccessor should be fine for this scenario. The recommendation in the Blazor docs is really about code running in the context of Blazor components. Blazor components are for client UI and are intended to be hosting model agnostic (Server/WebAssembly/Hybrid), so you shouldn't assume they run in the context of a request. But given that this is for auditing DB access, it doesn't sound like the Blazor restriction applies to your scenario.

@ghost
Copy link

ghost commented May 7, 2022

This issue has been resolved and has not had any activity for 1 day. It will be closed for housekeeping purposes.

See our Issue Management Policies for more information.

@ghost ghost closed this as completed May 7, 2022
@Bobetti
Copy link

Bobetti commented May 9, 2022

@mrlife @Bobetti The user identity established on the server is typically stored on the HttpContext. AuthenticationStateProvider is used for getting the authentication state on the client, which doesn't operate in the context of a request.

What prevents you from flowing the user from the HttpContext to where you need to do your user specific database queries?

@danroth27 Daniel, it would be a great help if you could guide how to do this. I do not really understand how to pass user data into dbconext.

@mrlife
Copy link
Contributor Author

mrlife commented May 11, 2022

The issue is kind of in the other direction. With the Audit.NET audit framework package, there is a configuration that's set up once in Startup.cs. There's code in that configuration that runs on the server after EF Core's SaveChanges completes that needs to access the user in order to log who made the change. Currently, the only option is to use HttpContextAccessor, which is not recommended because it is not guaranteed to work in Blazor apps.

@mrlife Does your configured Audit.NET code always run in the context of a request? If it does, then I think using IHttpContextAccessor should be fine for this scenario. The recommendation in the Blazor docs is really about code running in the context of Blazor components. Blazor components are for client UI and are intended to be hosting model agnostic (Server/WebAssembly/Hybrid), so you shouldn't assume they run in the context of a request. But given that this is for auditing DB access, it doesn't sound like the Blazor restriction applies to your scenario.

Hi @danroth27,

SaveChanges is called from a .razor page, so I'm not sure if that is in the context of the one http request Blazor uses or not. The configured Audit.NET code exists at the application level on the server.

I tell that code to find the user id with the following in Program.cs (app doesn't use Startup.cs):

app.Services.GetService<IHttpContextAccessor>()?.HttpContext?.User?.FindFirstValue("UserId");

@ghost ghost locked as resolved and limited conversation to collaborators Jun 10, 2022
This issue was closed.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-blazor Includes: Blazor, Razor Components Docs This issue tracks updating documentation feature-blazor-server feature-blazor-server-auth ✔️ Resolution: Answered Resolved because the question asked by the original author has been answered. question Status: Resolved
Projects
None yet
Development

No branches or pull requests

6 participants