Very recently I had a scenario where I have to implement two web apps. for both apps I had to implement authentication and authorization. I spent quite sometime researching on what is the better way to implement this & I hope this saves your time for anyone interested. anyway after doing my research I decided to use the following technologies...
1) IdentityServer4 :- for authentication & authorization
2) ASP.NET Identity :- User information storage
3) .NET API :- API protected by IdentityServer4
4) React :- React & Typescript Client App that is going to consume API
Lets start coding...
Step 1: Identity Server
We can either create empty project and do all the work by our self or we can use the one of the IdentityServer4 templates.to keep things simple I'm going to use one of the templates by running the following commands.
dotnet new -i identityserver4.templates
To see the installed templates run
dotnet new -l
There are couple of template options to choose from.in this case we want to use ASP.NET Identity as the user data storage so we will run this command:
dotnet new is4aspid
Now that we have our project ready its time to edit some of the code. find Config.cs
file which contains the identity configuration. the first this we are going to do is to add the Api resource:
public static IEnumerable < ApiResource > ApiResources => new ApiResource[] {
new ApiResource("sample.api", "Sample Api") {
Scopes = new string[] {
ProtectedApiScopes.ScannerApiScope.Name
}
}
};
Next step is to add our Client SPA app to the clients list:
public static IEnumerable < Client > Clients => new Client[]
{
new Client {
ClientId = "sample.spa",
ClientName = "Sample SPA",
AllowedGrantTypes = GrantTypes.Implicit,
AllowAccessTokensViaBrowser = true,
RequireConsent = false,
AccessTokenLifetime = 120,
RedirectUris = {
"http://localhost:3000"
},
PostLogoutRedirectUris = {
"http://localhost:3000"
},
AllowedCorsOrigins = {
"http://localhost:3000"
},
AllowedScopes = {
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
},
};
}
This is enough change for the identity configuration, now we have to add our configuration to IServiceCollection
in StartUp.cs
as follows:
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddInMemoryPersistedGrants()
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddInMemoryApiResources(Config.GetApiResources())
.AddInMemoryClients(Config.GetClients())
.AddAspNetIdentity<ApplicationUser>();
In production its better to store your identity server configuration in the database, but lets keep things simple for now.we are done configuring the identity server, next step is to create & setup the .NET api project.project can be created by running the following command:
dotnet new webapi
After creating the project we have to add reference to the IdentityServer4.AccessTokenValidation
package.we can then add configuration in the StartUp.cs
file by adding the following code
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options => {
options.Authority = "http://localhost:5000"; //Identity Server Uri
options.RequireHttpsMetadata = false;
options.ApiName = "sample.api";
});
That's it now we can simply guard any end point by adding [Authorize]
attribute on top of it:
[Authorize]
[HttpGet]
public IActionResult Get()
{
return Ok(new { Message= "You are authenticated" })
}
Next step is to create & setup our react app by running the following commands:
npx create-react-app sample-spa --template typescript
Or
yarn create react-app my-app --template typescript
After creating the react app. we will add the best oidc library called oidc-react which is the best oidc library I have seen by far.
npm install oidc-react
Create oidc-config.ts
file in the src directory of sample-spa project and add the following configuration
export const customOidcConfig: AuthProviderProps = {
clientId: 'sample.spa',
automaticSilentRenew: true,
redirectUri: 'http://localhost:3000/',
responseType: 'token id_token',
scope:"openid profile",
authority: 'http://localhost:5000',
onBeforeSignIn:()=>{
/**
* This method gets executed before signin redirecting begins
* it can be used to store Uri
*/
}
onSignIn:async (user: User | null)=>{
console.log('PATH',window.location.pathname)
if(user){
console.group('[ LOGIN: SUCCESS ]', user);
};
window.location.hash = '';
}
else{
console.error('[ LOGIN: ERRNO ]')
}
},
onSignOut:async (options?: AuthProviderSignOutProps) =>{
console.log('[ SignOutOpts ]', options);
}
};
Next step is to initiate login using the configuration above. find App.tsx
file and update it using the following code
<AuthProvider {...customOidcConfig}>
<AuthCheck/>
</AuthProvider>
This will automatically initiate the login process. we can also check if the user is logged in by using useAuth
hook.
export const AuthCheck: FC =() =>{
const authState = useAuth();
return (
<Fragment>
{
authState &&
authState.userData &&
<p>Authenticated</p>
}
{
(!authState ||
!authState.userData) &&
<p>Not Authenticated</p>
}
</Fragment>
)
}
Now we are done. I hope you enjoyed this.
Thanks for reading
Happy Coding!!!
Top comments (4)
why is identity server a paid service?
Its mainly because of financial issue. they explained their reason in detail in a blog post here. you can still use IdentityServer4 for free but latest versions are paid. you can find more information about the paid plan here
this is the problem with the industry, larger companies should allow for some kind of incentive to pay back to the community, especially for opensource. Patreon and the like would be helpful.
Indeed, but Duende(formerly known as IdentityServer) are providing special offering:
You can find their special offering here.