Reputation: 327
I have an issue with the silent refresh with oidc-client.
The signin works fine and I'm able to acquire a token.
However, the silent refresh doesn't fire, nothing happens. When I subscribe to methods that check token expiry (methods in subscribeevents
in authservice.ts below), these methods never fire - and the method isLoggedIn()
always return true even if the token has expired.
Here is my code :
import { Component, OnInit } from '@angular/core';
import { UserManager } from 'oidc-client';
import { getClientSettings } from '../openIdConnectConfig';
import { AuthService } from '../services/auth.service';
@Component({
selector: 'app-silentrefresh',
templateUrl: './silentrefresh.component.html',
styleUrls: ['./silentrefresh.component.css']
})
export class SilentRefreshComponent implements OnInit {
constructor(private _authService:AuthService) {
}
ngOnInit() {
this._authService.refreshCallBack();
}
}
Then my authservice :
import { UserManagerSettings, UserManager, User } from 'oidc-client';
import { Injectable } from '@angular/core';
import { getClientSettings } from '../openIdConnectConfig';
@Injectable()
export class AuthService {
private _manager = new UserManager(getClientSettings());
private _user: User = null;
constructor() {
this._manager.getUser().then(user => {
this._user = user;
});
this._manager.events.addUserLoaded(user => {
this._user = user;
});
this.subscribeevents();
}
public isLoggedIn(): boolean {
return this._user != null && !this._user.expired;
}
public getClaims(): any {
return this._user.profile;
}
public subscribeevents(): void {
this._manager.events.addSilentRenewError(() => {
console.log("error SilentRenew");
});
this._manager.events.addAccessTokenExpiring(() => {
console.log("access token expiring");
});
this._manager.events.addAccessTokenExpired(() => {
console.log("access token expired");
});
}
public refreshCallBack(): void {
console.log("start refresh callback");
this._manager.signinSilentCallback()
.then(data => { console.log("suucess callback") })
.catch(err => {
console.log("err callback");
});
console.log("end refresh callback");
}
getUser(): any {
return this._user;
}
getName(): any {
return this._user.profile.name;
}
getAuthorizationHeaderValue(): string {
return `${this._user.token_type} ${this._user.access_token}`;
}
startAuthentication(): Promise<void> {
return this._manager.signinRedirect();
}
completeAuthentication(): Promise<void> {
return this._manager.signinRedirectCallback().then(user => {
this._user = user;
});
}
}
And my config:
import { UserManagerSettings } from "oidc-client";
export function getClientSettings(): UserManagerSettings {
return {
authority: 'https://login.microsoftonline.com/136544d9-038e-4646-afff-10accb370679',
client_id: '257b6c36-1168-4aac-be93-6f2cd81cec43',
redirect_uri: 'http://localhost:4200/auth-callback',
//redirect_uri: 'https://demoazureadconnectangular5.azurewebsites.net/auth-callback',
post_logout_redirect_uri: 'http://localhost:4200/',
//post_logout_redirect_uri: 'https://demoazureadconnectangular5.azurewebsites.net/',
response_type: "id_token",
scope: "openid profile",
filterProtocolClaims: true,
loadUserInfo: true,
automaticSilentRenew: true,
silent_redirect_uri: 'http://localhost:4200/assets/silentrefresh',
metadata: {
issuer: "https://sts.windows.net/136544d9-038e-4646-afff-10accb370679/",
authorization_endpoint: "https://login.microsoftonline.com/136544d9-038e-4646-afff-10accb370679/oauth2/authorize",
token_endpoint: "https://login.microsoftonline.com/136544d9-038e-4646-afff-10accb370679/oauth2/token",
//jwks_uri: "https://login.microsoftonline.com/common/discovery/keys",
jwks_uri: "http://localhost:4200/assets/keys.json",
//jwks_uri: "https://demoazureadconnectangular5.azurewebsites.net/assets/keys.json",
//jwks_uri: "http://localhost:50586/api/keys",
signingKeys: [{ "ApiAccessKey": "NgixniZ0S1JHxo7GPEZYa38OBTxSA98AqJKDX5XqsJ8=" }]
}
};
}
I also tried to use a static page like this:
<head>
<title></title>
</head>
<body>
<script src="oidc-client.min.js"></script>
<script>
var usermanager = UserManager().signinSilentCallback()
.catch((err) => {
console.log(err);
});
</script>
</body>
It's never fired neither
In order to test, I've changed the ID token expiry to 10 min. I use Azure AD Connect (Open Id Connect in Azure) and Microsoft says it's not fully compatible with Open ID Connect standard... So I don't know if it's on my side or Azure side.
Somebody can help me to solve this?
Upvotes: 11
Views: 22458
Reputation: 551
Sharing information just in case if it helps anyone.
I also faced this same issue but in my case, silent userManager.signinSilent
was not working in Chrome incognito but when I test it on normal chrome window then it was working as expected.
Root cause: This issue happens due to Cookies settings:
Error in chrome console :
A cookie associated with a cross-site resource at https://example.com/ was set without the SameSite attribute. It has been blocked, as Chrome now only delivers cookies with cross-site requests if they are set with SameSite=None and Secure.
Solution: You will need allow thirds party cookies in Chrome setting OR you can also add particular site to allow cookies:
Chrome setting: At the top right, click More More and then Settings -> Click Privacy and security -> Cookies and other site data.
Upvotes: 1
Reputation: 1177
Check if you have correct redirect URI in the database.
Check you have added the following in your angular.json
file:
...
"assets": [
"src/assets",
"silent-refresh.html",
"oidc-client.min.js"
.....
],
...
Check silent-refresh.html
:
<script src="oidc-client.min.js"></script><script>
var mgr = new Oidc.UserManager();
mgr.signinSilentCallback().catch(error => {
console.error(error);
});
</script>
Check you do not create more than one instance of UserManager
You can do either way - automaticSilentRenew: false
, or automaticSilentRenew: true,
I will recommend using automaticSilentRenew: false
and trigger an event on expiring.
https://github.com/IdentityModel/oidc-client-js/wiki
public renewToken() {
return this.manager.signinSilent().then(u => {
this.user = u;
}).catch(er => {
console.log(er);
});
}
this.manager.events.addAccessTokenExpiring(x => {
console.log('Acess token expiring event');
this.renewToken().then(u => {
console.log('Acess token expiring event renew success');
});
});
If the above things do not work then check the identity server code.
Startup
services.AddIdentityServer(options =>
{
options.Authentication.CookieLifetime = TimeSpan.FromDays(30);
options.Authentication.CookieSlidingExpiration = true;
});
services.AddAuthentication(x => x.DefaultAuthenticateScheme = IdentityServerConstants.DefaultCookieAuthenticationScheme);
Logout
await HttpContext.SignOutAsync(IdentityServer4.IdentityServerConstants.DefaultCookieAuthenticationScheme);
Thanks to https://github.com/IdentityModel/oidc-client-js/issues/911#issuecomment-617724445
Upvotes: 4
Reputation: 201
Simplest reason can be, not adding silent renew url as a redirect url in identity server configuration.
In your identity server database, redirect urls for your clients should be like this
redirectUrls: [http://localhost:4200/assets/silentrefresh, http://localhost:4200/auth-callback]
Upvotes: 1
Reputation: 1161
The problem is that you are not asking access_token from azure AD, only id_token. You must set response_type to id_token token to get both tokens. This change will need also few more parameters. For example resource for your backend. I have answered similar question here. I'm using also Angular 5 and oidc client. https://stackoverflow.com/a/50922730/8081009 And I answer you here also before https://github.com/IdentityModel/oidc-client-js/issues/504#issuecomment-400056662 Here is what you need to set to get silent renew working.
includeIdTokenInSilentRenew: true
extraQueryParams: {
resource: '10282f28-36ed-4257-a853-1bf404996b18'
}
response_type: 'id_token token',
scope: 'openid'
loadUserInfo: false,
automaticSilentRenew: true,
silent_redirect_uri: `${window.location.origin}/silent-refresh.html`,
metadataUrl: 'https://login.microsoftonline.com/YOUR_TENANT_NAME.onmicrosoft.com/.well-known/openid-configuration',
signingKeys: [
add here keys from link below
]
https://login.microsoftonline.com/common/discovery/keys
I'm also using different static page for callback endpoint with silent renew because this way user won't notice a thing. This page is minimum possible so oidc won't load whole angular application to hidden iframe what it is using for silent renew. So this is recommended to be more efficient.
<head>
<title></title>
</head>
<body>
<script src="assets/oidc-client.min.js"></script>
<script>
new Oidc.UserManager().signinSilentCallback()
.catch((err) => {
console.log(err);
});
</script>
</body>
Upvotes: 3
Reputation: 799
Not sure what oidc-client.js version you are using, this should never have worked.
```
new Oidc.UserManager().signinSilentCallback()
.catch((err) => {
console.log(err);
});
``
Usermanager is in **Oidc** object.
Upvotes: 0
Reputation: 6809
I have used some different approach to initate the silentRenw, instead of using
automaticSilentRenew: true,
I decided to explicitly call the signInSilent();. Reason for doing the i was facing some issues as automaticSilentRenew: true, was not working as expected.
I initialized the event and method in my UserAuth class constructor that implements my interface
constructor(private oidcSettings: CoreApi.Interfaces.Authentication.OpenIDConnectionSettings)
{
this.userManager.events.addAccessTokenExpiring(() =>
{
this.userManager.signinSilent({scope: oidcSettings.scope, response_type: oidcSettings.response_type})
.then((user: Oidc.User) =>
{
this.handleUser(user);
})
.catch((error: Error) =>
{
//Work around to handle to Iframe window timeout errors on browsers
this.userManager.getUser()
.then((user: Oidc.User) =>
{
this.handleUser(user);
});
});
});
}
Where as handleUser is just check for logged in user.
So if you initialize the signInSilent process in your constructor and then call signInSilent complete i.e. callback it may work.
Upvotes: 0