1
1
/*
2
- * Copyright 2014 Stephen Cummins
2
+ * Copyright 2025 Barna Magyarkuti
3
3
*
4
4
* Licensed under the Apache License, Version 2.0 (the "License");
5
5
* you may not use this file except in compliance with the License.
@@ -52,9 +52,9 @@ public class MicrosoftAuthenticator implements IOAuth2Authenticator {
52
52
static final int CREDENTIAL_CACHE_TTL_MINUTES = 10 ;
53
53
// TODO: why do we cache idTokens? Why don't we just pass them around in code?
54
54
static Cache <String , String > credentialStore = CacheBuilder
55
- .newBuilder ()
56
- .expireAfterAccess (CREDENTIAL_CACHE_TTL_MINUTES , TimeUnit .MINUTES )
57
- .build ();
55
+ .newBuilder ()
56
+ .expireAfterAccess (CREDENTIAL_CACHE_TTL_MINUTES , TimeUnit .MINUTES )
57
+ .build ();
58
58
59
59
private final String scopes = "email" ;
60
60
private final String callbackURL = "http://localhost:8004/auth/microsoft/callback" ;
@@ -65,17 +65,17 @@ public class MicrosoftAuthenticator implements IOAuth2Authenticator {
65
65
66
66
@ Inject
67
67
public MicrosoftAuthenticator (
68
- @ Named (Constants .MICROSOFT_CLIENT_ID ) final String clientId ,
69
- @ Named (Constants .MICROSOFT_TENANT_ID ) final String tenantId ,
70
- @ Named (Constants .MICROSOFT_SECRET ) final String clientSecret ,
71
- @ Named (Constants .MICROSOFT_JWKS_URL ) final String jwksUrl
68
+ @ Named (Constants .MICROSOFT_CLIENT_ID ) final String clientId ,
69
+ @ Named (Constants .MICROSOFT_TENANT_ID ) final String tenantId ,
70
+ @ Named (Constants .MICROSOFT_SECRET ) final String clientSecret ,
71
+ @ Named (Constants .MICROSOFT_JWKS_URL ) final String jwksUrl
72
72
) throws MalformedURLException {
73
73
this .clientId = clientId ;
74
74
this .tenantId = tenantId ;
75
75
this .clientSecret = clientSecret ;
76
76
provider = new UrlJwkProvider (new URL (jwksUrl ));
77
77
}
78
-
78
+
79
79
@ Override
80
80
public AuthenticationProvider getAuthenticationProvider () {
81
81
return AuthenticationProvider .MICROSOFT ;
@@ -89,11 +89,11 @@ public String getFriendlyName() {
89
89
@ Override
90
90
public String getAuthorizationUrl (String antiForgeryStateToken ) {
91
91
var parameters = AuthorizationRequestUrlParameters
92
- .builder (callbackURL , Collections .singleton (scopes ))
93
- .responseMode (ResponseMode .QUERY )
94
- .prompt (Prompt .SELECT_ACCOUNT )
95
- .state (antiForgeryStateToken )
96
- .build ();
92
+ .builder (callbackURL , Collections .singleton (scopes ))
93
+ .responseMode (ResponseMode .QUERY )
94
+ .prompt (Prompt .SELECT_ACCOUNT )
95
+ .state (antiForgeryStateToken )
96
+ .build ();
97
97
return client ().getAuthorizationRequestUrl (parameters ).toString ();
98
98
}
99
99
@@ -111,11 +111,11 @@ public String extractAuthCode(String url) {
111
111
}
112
112
113
113
@ Override
114
- public String exchangeCode (String authorizationCode ) throws CodeExchangeException {
114
+ public String exchangeCode (String authorizationCode ) throws CodeExchangeException {
115
115
try {
116
116
var authParams = AuthorizationCodeParameters
117
- .builder (authorizationCode , new URI (callbackURL )).scopes (Collections .singleton (scopes ))
118
- .build ();
117
+ .builder (authorizationCode , new URI (callbackURL )).scopes (Collections .singleton (scopes ))
118
+ .build ();
119
119
var result = client ().acquireToken (authParams ).get ();
120
120
121
121
String internalCredentialID = UUID .randomUUID ().toString ();
@@ -135,23 +135,23 @@ public UserFromAuthProvider getUserInfo(String internalProviderReference) throws
135
135
var token = parseAndVerifyToken (tokenStr );
136
136
// TODO: to support sign-ups, parse more info
137
137
return new UserFromAuthProvider (
138
- token .getSubject (), null , null , token .getClaim ("email" ).asString (),
139
- null , null , null , null , null , null
138
+ token .getSubject (), null , null , token .getClaim ("email" ).asString (),
139
+ null , null , null , null , null , null
140
140
);
141
141
}
142
142
143
143
private ConfidentialClientApplication client () {
144
144
var secret = ClientCredentialFactory .createFromSecret (clientSecret );
145
145
try {
146
146
return ConfidentialClientApplication .builder (clientId , secret )
147
- .authority (String .format ("https://login.microsoftonline.com/%s" , tenantId ))
148
- .build ();
147
+ .authority (String .format ("https://login.microsoftonline.com/%s" , tenantId ))
148
+ .build ();
149
149
} catch (MalformedURLException e ) {
150
150
throw new RuntimeException (e );
151
151
}
152
152
}
153
153
154
- private DecodedJWT parseAndVerifyToken (String tokenStr ) throws AuthenticatorSecurityException {
154
+ private DecodedJWT parseAndVerifyToken (String tokenStr ) throws AuthenticatorSecurityException {
155
155
// validating id token based on requirements at
156
156
// https://learn.microsoft.com/en-us/entra/identity-platform/id-tokens
157
157
// I've ignored "nonce" validation as RaspberryPi Authenticator also skips it
@@ -161,27 +161,16 @@ private DecodedJWT parseAndVerifyToken (String tokenStr) throws AuthenticatorSec
161
161
var jwk = provider .get (keyId );
162
162
var algorithm = Algorithm .RSA256 ((RSAPublicKey ) jwk .getPublicKey ());
163
163
var verifier = JWT .require (algorithm )
164
- .withAudience (clientId )
165
- .withIssuer (String .format ("https://login.microsoftonline.com/%s/v2.0" , tenantId ))
166
- .build ();
164
+ .withAudience (clientId )
165
+ .withIssuer (String .format ("https://login.microsoftonline.com/%s/v2.0" , tenantId ))
166
+ .build ();
167
167
verifier .verify (tokenStr ); // TODO: does this check validity of cert?
168
168
if (null == keyId ) {
169
169
throw new AuthenticatorSecurityException ("Token verification: NO_KEY_ID" );
170
170
}
171
- if (null == token .getExpiresAt ()) {
172
- throw new AuthenticatorSecurityException ("Token verification: NULL_EXPIRY" );
173
- }
174
- if (null == token .getIssuedAt ()) {
175
- throw new AuthenticatorSecurityException ("Token verification: NULL_ISSUED_AT" );
176
- }
177
- if (null == token .getNotBefore ()) {
178
- throw new AuthenticatorSecurityException ("Token verification: NULL_NOT_BEFORE" );
179
- }
180
- }
181
- catch (InvalidPublicKeyException e ) {
171
+ } catch (InvalidPublicKeyException e ) {
182
172
throw new AuthenticatorSecurityException ("Token verification: INVALID_PUBLIC_KEY" );
183
- }
184
- catch (JwkException e ) {
173
+ } catch (JwkException e ) {
185
174
throw new AuthenticatorSecurityException (e .getMessage ());
186
175
}
187
176
return token ;
0 commit comments