Problem
We have SPA on Angular and asp.net core. We want to shift the responsobility for users authentification on external oauth server.
Solution
Angular
Install package
npm i angular-oauth2-oidc --save
Importing module in app.module.ts
imports: [
...,
OAuthModule.forRoot()
]
Create config in app folder app.auth.config.ts
import { AuthConfig } from 'angular-oauth2-oidc';
export const authConfig: AuthConfig = {
// Url of the Identity Provider
issuer: 'https://...',
// URL of the SPA to redirect the user to after login
redirectUri: window.location.origin + '/',
// The SPA's id. The SPA is registerd with this id at the auth-server
clientId: 'clienId',
// set the scope for the permissions the client should request
// The first three are defined by OIDC. The 4th is a usecase-specific one
scope: 'openid offline profile.read',
}
In app.component.ts
import { OAuthService } from 'angular-oauth2-oidc';
import { JwksValidationHandler } from 'angular-oauth2-oidc';
import { Component } from '@angular/core';
import { authConfig } from './app.auth.config';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
title = 'ClientApp';
constructor(private oauthService: OAuthService) {
this.configureWithNewConfigApi();
}
private configureWithNewConfigApi() {
this.oauthService.configure(authConfig);
this.oauthService.tokenValidationHandler = new JwksValidationHandler();
this.oauthService.loadDiscoveryDocumentAndLogin();
}
}
This row says if there is no token goto provider to get it
this.oauthService.loadDiscoveryDocumentAndLogin();
At the writting post time, angular-oauth2-oidc liblary supports only one response type ```id_token token```, so your oauth2 provider should allow such response type for your client.
When you run your app, it goes to issuer ```.well-known/openid-configuration``` and get information about oauth provider
```json
{
"issuer": "https://issuer-address/",
"authorization_endpoint": "https://issuer-address/oauth2/auth",
"token_endpoint": "https://issuer-address/oauth2/token",
"jwks_uri": "https://issuer-address/.well-known/jwks.json",
"subject_types_supported": [
"public"
],
"response_types_supported": [
"code",
"code id_token",
"id_token",
"token id_token",
"token",
"token id_token code"
],
"claims_supported": [
"sub"
],
"grant_types_supported": [
"authorization_code",
"implicit",
"client_credentials",
"refresh_token"
],
"response_modes_supported": [
"query",
"fragment"
],
"userinfo_endpoint": "https://issuer-address/userinfo",
"scopes_supported": [
"offline",
"openid"
],
"token_endpoint_auth_methods_supported": [
"client_secret_post",
"client_secret_basic",
"private_key_jwt",
"none"
],
"userinfo_signing_alg_values_supported": [
"none",
"RS256"
],
"id_token_signing_alg_values_supported": [
"RS256"
],
"request_parameter_supported": true,
"request_uri_parameter_supported": true,
"require_request_uri_registration": true,
"claims_parameter_supported": false
}
Then it redirects to
https://issuer-address/oauth2/auth?response_type=id_token token&client_id=clientId&state=F7Jju1p1hL1kGy7J6gTSpFRqiLvqcF6WHZLPT32i&redirect_uri=https://localhost:44366&scope=openid offline profile.read&nonce=F7Jju1p1hL1kGy7J6gTSpFRqiLvqcF6WHZLPT32i
issuer in it’s turn redirect to login page
after user type login and password there are few redirects to ```/oauth2/auth``` , ```/consent``` and again ```oauth2/auth```
last one redirects to
```https://localhost:44366#access_token=access_token&expires_in=3599&id_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c&token_type=bearer```
You can see here access_token and id_token. Actually, id-token is jwt token. If you goto this [website](https://jwt.io/) you can decode jwt and see encoded information. Note, jwt token also contains signature of conatainig data.
Now, your frontend app is authentificated, however backed knows nothing about it. So we have to send our token to backed using Authorization header. Backend in its turn has to validate jwt token using issuer public key and token signature. So it can sure that data was not changed and trust user.
It is pretty easy to implement with asp.net core. All you need to add middleware in your ```Startup.cs``` ,```ConfigureServices``` method
```c#
// configure jwt authentication
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(x =>
{
x.RequireHttpsMetadata = false;
x.SaveToken = true;
x.Authority = "oauth_server_address";
x.IncludeErrorDetails = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
ValidAudience = "",
ValidateIssuer = false,
ValidateAudience = false
};
});
Don’t forget to add in Configure
mehtod
app.UseAuthentication();
This middleware runs on every request, take jwt token from authorization header, validate it and extract user information. Also it goes to oauth server https://issuer-address/.well-known/jwks.json
and obtain public keys for validation purpose.
Now we can disallow access to our actions or contoller using [Authorize]
data attirubute. For example,
[Authorize]
public class MyController : ControllerBase
And last step, we must send authorization header with each request to actions which require authentication.
Following angular-oauth2-oidc
library tutorial you can set sendAccessToken
to true for automate sending token. However it was not working for me at time writting moment.
So I have to add header explicitly
const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
"Authorization": "Bearer " + this.oauthService.getIdToken()
})
};
//this.baseUrl = baseUrl;
this.http.get<IModel[]>('/api/contorller', httpOptions).subscribe(result => {
this.generators = result;
}, error => console.error(error));